Mantid
Loading...
Searching...
No Matches
ParameterEstimator.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 +
9
13
14#include "MantidKernel/Logger.h"
15
16#include <cmath>
17#include <mutex>
18
20
21using namespace Functions;
22
24Kernel::Logger g_log("ParameterEstimator");
25
26namespace {
28std::recursive_mutex FUNCTION_MAP_MUTEX;
29} // namespace
30
32using FunctionMapType = std::map<std::string, std::pair<size_t, Function>>;
33
34//----------------------------------------------------------------------------------------------
35
38void initFunctionLookup(FunctionMapType &functionMapType) {
39 assert(functionMapType.empty());
40
41 functionMapType["Gaussian"] = std::make_pair(2, Gaussian);
42 functionMapType["Lorentzian"] = std::make_pair(2, Lorentzian);
43 functionMapType["BackToBackExponential"] = std::make_pair(4, BackToBackExponential);
44}
45
49 std::lock_guard<std::recursive_mutex> lock(FUNCTION_MAP_MUTEX);
50
51 static FunctionMapType functionMapType;
52
53 if (functionMapType.empty())
54 initFunctionLookup(functionMapType);
55 return functionMapType;
56}
57
61 const FunctionMapType &functionMap = getFunctionMapType();
62 auto index = functionMap.find(function.name());
63 if (index != functionMap.end()) {
64 if (!function.isExplicitlySet(index->second.first))
65 return index->second.second;
66 }
67 return None;
68}
69
70//----------------------------------------------------------------------------------------------
74 if (auto cf = dynamic_cast<const API::CompositeFunction *>(&function)) {
75 for (size_t i = 0; i < cf->nFunctions(); ++i) {
76 if (needSettingInitialValues(*cf->getFunction(i)))
77 return true;
78 }
79 } else {
80 return whichFunction(function) != None;
81 }
82 return false;
83}
84
85//----------------------------------------------------------------------------------------------
91void extractValues(const API::FunctionDomain1D &domain, const API::FunctionValues &values, std::vector<double> &x,
92 std::vector<double> &y) {
93
94 size_t n = domain.size();
95 double start = domain[0];
96 double end = domain[n - 1];
97 auto dBegin = domain.getPointerAt(0);
98 auto startIter = std::lower_bound(dBegin, dBegin + n, start);
99 auto istart = static_cast<size_t>(std::distance(dBegin, startIter));
100 if (istart == n) {
101 x.clear();
102 y.clear();
103 return;
104 }
105 auto endIter = std::lower_bound(startIter, dBegin + n, end);
106 auto iend = static_cast<size_t>(std::distance(dBegin, endIter));
107 if (iend <= istart) {
108 x.clear();
109 y.clear();
110 return;
111 }
112 n = iend - istart;
113 x.resize(n);
114 y.resize(n);
115 for (size_t i = istart; i < iend; ++i) {
116 auto j = i - istart;
117 x[j] = domain[i];
118 y[j] = values.getFitData(i);
119 }
120}
121
122// Functions to extract features from a curve
123
124//----------------------------------------------------------------------------------------------
134std::pair<double, double> getPeakLeftRightWidth(double centre, const SimpleChebfun &der2, size_t n = 1) {
135 double left = centre;
136 double right = centre;
137
138 auto &xp = der2.xPoints();
139 auto roots = der2.roughRoots();
140 if (!roots.empty()) {
141 auto iright = std::upper_bound(roots.begin(), roots.end(), centre);
142 if (iright == roots.end()) {
143 left = roots.back();
144 return std::make_pair(left, right);
145 }
146 if (static_cast<size_t>(std::distance(roots.begin(), iright)) < n) {
147 left = xp.front();
148 return std::make_pair(left, right);
149 }
150 left = *(iright - n);
151 if (static_cast<size_t>(std::distance(iright, roots.end())) < n) {
152 right = xp.back();
153 } else {
154 right = *(iright + n - 1);
155 }
156 }
157 return std::make_pair(left, right);
158}
159
160//----------------------------------------------------------------------------------------------
165std::pair<double, double> getPeakLeftRightExtent(double centre, const SimpleChebfun &der2) {
166 return getPeakLeftRightWidth(centre, der2, 2);
167}
168
169//----------------------------------------------------------------------------------------------
176std::pair<double, double> getPeakHWHM(double centre, double height, const SimpleChebfun &fun) {
177 auto roots = fun.roughRoots(height / 2);
178 double left = fun.startX();
179 double right = fun.endX();
180 if (roots.empty()) {
181 return std::make_pair(left - centre, right - centre);
182 } else if (roots.size() == 1) {
183 if (roots.front() < centre) {
184 left = roots.front();
185 } else {
186 right = roots.front();
187 }
188 } else {
189 auto iright = std::upper_bound(roots.begin(), roots.end(), centre);
190 if (iright == roots.end()) {
191 left = roots.back();
192 } else if (iright == roots.begin()) {
193 right = roots.front();
194 } else {
195 left = *(iright - 1);
196 right = *iright;
197 }
198 }
199 return std::make_pair(left - centre, right - centre);
200}
201
202//----------------------------------------------------------------------------------------------
206double getPeakWidth(double centre, const SimpleChebfun &der2) {
207 auto leftRight = getPeakLeftRightWidth(centre, der2);
208 return fabs(leftRight.second - leftRight.first) / 2;
209}
210
211//----------------------------------------------------------------------------------------------
216double getPeakCentre(double centre, const SimpleChebfun &der1) {
217 auto roots = der1.roughRoots();
218 if (!roots.empty()) {
219 double minDiff = der1.width();
220 size_t n = roots.size();
221 size_t imin = n;
222 for (size_t i = 0; i < n; ++i) {
223 auto dx = fabs(roots[i] - centre);
224 if (dx < minDiff) {
225 minDiff = dx;
226 imin = i;
227 }
228 }
229 if (imin < n)
230 return roots[imin];
231 }
232 return centre;
233}
234
237public:
238 LinearFunction(double a0, double a1) : m_a0(a0), m_a1(a1) {}
239 double operator()(double x) const { return m_a0 + m_a1 * x; }
240
241private:
242 double m_a0;
243 double m_a1;
244};
245
246//----------------------------------------------------------------------------------------------
253 const SimpleChebfun &der2) {
254 // Find the actual peak centre and gaussian component of the width
255 auto centre = getPeakCentre(function.getParameter("X0"), der1);
256 double sigma = getPeakWidth(centre, der2);
257 if (sigma == 0.0)
258 sigma = 1e-06;
259 function.setParameter("S", sigma);
260
261 g_log.debug() << "Estimating parameters of BackToBackExponential\n";
262 g_log.debug() << "centre= " << centre << '\n';
263 g_log.debug() << "sigma = " << sigma << '\n';
264
265 // Estimate the background level
266 auto xlr = getPeakLeftRightExtent(centre, der2);
267 g_log.debug() << "extent: " << xlr.first - centre << ' ' << xlr.second - centre << '\n';
268 double yl = fun(xlr.first);
269 double yr = fun(xlr.second);
270 double slope = (yr - yl) / (xlr.second - xlr.first);
271 double background = yl + slope * (centre - xlr.first);
272 double height = fun(centre) - background;
273 g_log.debug() << "height= " << height << '\n';
274 g_log.debug() << "background= " << background << '\n';
275 g_log.debug() << "slope= " << slope << '\n';
276
277 // Remove the background as it affects A and B parameters.
278 LinearFunction bg(-yl + slope * xlr.first, -slope);
279 SimpleChebfun fun1(fun);
280 fun1 += bg;
281 // Find left and right "HWHM".
282 auto hwhm = getPeakHWHM(centre, height, fun1);
283 g_log.debug() << "HWHM: " << hwhm.first << ' ' << hwhm.second << '\n';
284
285 // Find the extent of the default fitting function (with new S set)
286 // to be able to make an approximation with a SimpleChebfun.
287 function.setParameter("I", 1.0);
288 double x0 = function.getParameter("X0");
289 double leftX = 1.0 / function.getParameter("A");
290 if (leftX < 3 * sigma) {
291 leftX = 3 * sigma;
292 }
293 leftX = x0 - leftX;
294 double rightX = 1.0 / function.getParameter("B");
295 if (rightX < 3 * sigma) {
296 rightX = 3 * sigma;
297 }
298 rightX = x0 + rightX;
299
300 // Find corrections to the default A and B parameters.
301 // A and B are responsible for differences in the widths.
302 {
303 SimpleChebfun b2b(fun.order(), function, leftX, rightX);
304 auto b2b_d1 = b2b.derivative();
305 auto centre1 = getPeakCentre(x0, b2b_d1);
306
307 double height1 = b2b(centre1);
308 auto hwhm1 = getPeakHWHM(centre1, height1, b2b);
309 g_log.debug() << "new HWHM: " << hwhm1.first << ' ' << hwhm1.second << '\n';
310
311 double denom = hwhm.first + sigma;
312 double aCorr = denom > 0 ? (hwhm1.first + sigma) / denom : 100.0;
313 if (aCorr < 0) {
314 aCorr = 100.0;
315 function.fix(2);
316 }
317 denom = hwhm.second - sigma;
318 double bCorr = denom > 0 ? (hwhm1.second - sigma) / denom : 100.0;
319 if (bCorr < 0) {
320 bCorr = 100.0;
321 function.fix(3);
322 }
323 g_log.debug() << "corrections: " << aCorr << ' ' << bCorr << '\n';
324 double a = function.getParameter("A") * aCorr;
325 double b = function.getParameter("B") * bCorr;
326 function.setParameter("A", a);
327 function.setParameter("B", b);
328 }
329
330 // After all shape parameters are set (S, A and B) shift X0
331 // and scale I.
332 {
333 SimpleChebfun b2b(fun.order(), function, leftX, rightX);
334 auto b2b_d1 = b2b.derivative();
335 double centre1 = getPeakCentre(x0, b2b_d1);
336 double height1 = b2b(centre1);
337 x0 += centre - centre1;
338 function.setParameter("X0", x0);
339 function.setParameter("I", height / height1);
340 }
341
342 g_log.debug() << "Parameters:\n";
343 g_log.debug() << "I " << function.getParameter("I") << '\n';
344 g_log.debug() << "X0 " << function.getParameter("X0") << '\n';
345 g_log.debug() << "A " << function.getParameter("A") << '\n';
346 g_log.debug() << "B " << function.getParameter("B") << '\n';
347 g_log.debug() << "S " << function.getParameter("S") << '\n';
348}
349
350//----------------------------------------------------------------------------------------------
356void setValues(API::IFunction &function, const SimpleChebfun &fun, const SimpleChebfun &der1,
357 const SimpleChebfun &der2) {
358 if (auto cf = dynamic_cast<const API::CompositeFunction *>(&function)) {
359 for (size_t i = 0; i < cf->nFunctions(); ++i) {
360 setValues(*cf->getFunction(i), fun, der1, der2);
361 }
362 return;
363 }
364
365 switch (whichFunction(function)) {
366 case Gaussian: {
367 double width = getPeakWidth(function.getParameter("PeakCentre"), der2);
368 function.setParameter("Sigma", width);
369 break;
370 }
371 case Lorentzian: {
372 double width = getPeakWidth(function.getParameter("PeakCentre"), der2);
373 function.setParameter("FWHM", width);
374 break;
375 }
377 setBackToBackExponential(function, fun, der1, der2);
378 break;
379 }
380 default:
381 break;
382 }
383}
384
385//----------------------------------------------------------------------------------------------
391void estimate(API::IFunction &function, const API::FunctionDomain1D &domain, const API::FunctionValues &values) {
392 if (!needSettingInitialValues(function))
393 return;
394 std::vector<double> x;
395 std::vector<double> y;
396 extractValues(domain, values, x, y);
397 if (x.empty())
398 return;
399 SimpleChebfun fun(x, y);
400 auto der1 = fun.derivative();
401 auto der2 = der1.derivative();
402 setValues(function, fun, der1, der2);
403}
404
405} // namespace Mantid::CurveFitting::ParameterEstimator
double sigma
Definition: GetAllEi.cpp:156
double height
Definition: GetAllEi.cpp:155
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
double left
Definition: LineProfile.cpp:80
double right
Definition: LineProfile.cpp:81
#define fabs(x)
Definition: Matrix.cpp:22
A composite function is a function containing other functions.
Represent a domain for functions of one real argument.
size_t size() const override
Return the number of arguments in the domain.
const double * getPointerAt(size_t i) const
Get a pointer to i-th value.
A class to store values calculated by a function.
double getFitData(size_t i) const
Get a fitting data value.
This is an interface to a fitting function - a semi-abstarct class.
Definition: IFunction.h:163
virtual double getParameter(size_t i) const =0
Get i-th parameter.
virtual void setParameter(size_t, const double &value, bool explicitlySet=true)=0
Set i-th parameter.
virtual std::string name() const =0
Returns the function's name.
void fix(size_t i, bool isDefault=false)
Removes a parameter i from the list of active.
Definition: IFunction.cpp:181
virtual bool isExplicitlySet(size_t i) const =0
Checks if a parameter has been set explicitly.
Provide BackToBackExponential peak shape function interface to IPeakFunction.
Provide gaussian peak shape function interface to IPeakFunction.
Definition: Gaussian.h:36
Provide lorentzian peak shape function interface to IPeakFunction.
Definition: Lorentzian.h:32
SimpleChebfun : approximates smooth 1d functions and provides methods to manipulate them.
Definition: SimpleChebfun.h:21
std::vector< double > roughRoots(double level=0.0) const
Get rough estimates of the roots.
SimpleChebfun derivative() const
Create a derivative of this function.
double width() const
Get the width of the interval.
Definition: SimpleChebfun.h:44
double startX() const
Start of the interval.
Definition: SimpleChebfun.h:40
const std::vector< double > & xPoints() const
Get a reference to the x-points.
Definition: SimpleChebfun.h:46
double endX() const
End of the interval.
Definition: SimpleChebfun.h:42
size_t order() const
Order of the approximating polynomial.
Definition: SimpleChebfun.h:36
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition: Logger.h:52
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
Kernel::Logger g_log("ExperimentInfo")
static logger object
Function whichFunction(const API::IFunction &function)
Return a function code for a function if it needs setting values or None otherwise.
void setValues(API::IFunction &function, const SimpleChebfun &fun, const SimpleChebfun &der1, const SimpleChebfun &der2)
Set initial values to a function if it needs to.
bool needSettingInitialValues(const API::IFunction &function)
Test if initial values need to be set before fitting.
void MANTID_CURVEFITTING_DLL estimate(API::IFunction &function, const API::FunctionDomain1D &domain, const API::FunctionValues &values)
ParameterEstimator estimates parameter values of some fitting functions from fitting data.
void extractValues(const API::FunctionDomain1D &domain, const API::FunctionValues &values, std::vector< double > &x, std::vector< double > &y)
Extract values from domain and values objects to vectors.
std::pair< double, double > getPeakHWHM(double centre, double height, const SimpleChebfun &fun)
Get displacements from peak centre where peak reaches half the maximum.
double getPeakCentre(double centre, const SimpleChebfun &der1)
Improve an estimate of a peak centre.
std::pair< double, double > getPeakLeftRightExtent(double centre, const SimpleChebfun &der2)
Get the peak's extent on either side of the centre.
void initFunctionLookup(FunctionMapType &functionMapType)
Initializes a FunctionMapType object.
std::pair< double, double > getPeakLeftRightWidth(double centre, const SimpleChebfun &der2, size_t n=1)
Get a measure of peak's width.
const FunctionMapType & getFunctionMapType()
Returns a reference to the static functionMapType.
std::map< std::string, std::pair< size_t, Function > > FunctionMapType
void setBackToBackExponential(API::IFunction &function, const SimpleChebfun &fun, const SimpleChebfun &der1, const SimpleChebfun &der2)
Set initial values to a BackToBackExponential.
double getPeakWidth(double centre, const SimpleChebfun &der2)
Estimate a peak width from a second derivative of the data.