Mantid
Loading...
Searching...
No Matches
PseudoVoigt.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 +
10#include "MantidKernel/Logger.h"
11
12#include <iostream>
13
14#include <cmath>
15
16namespace {
17
18const double LN2_DIV_PI = 2. * std::sqrt(M_LN2 / M_PI);
19constexpr double LN2_M4 = 4 * M_LN2;
20
25inline double cal_ag(const double gamma) { return LN2_DIV_PI / gamma; }
26
31inline constexpr double cal_bg(const double gamma) { return LN2_M4 / (gamma * gamma); }
32
39inline double cal_gaussian(const double ag, const double bg, const double xdiffsq) {
40 return ag * std::exp(-bg * xdiffsq);
41}
42
49inline constexpr double cal_lorentzian(const double gamma_div_2, const double gammasq_div_4, const double xdiffsq) {
50 return gamma_div_2 / (xdiffsq + gammasq_div_4) / M_PI;
51}
52
53} // namespace
54
56
57using namespace CurveFitting;
58using namespace Constraints;
59using namespace std;
60using namespace API;
61
62namespace {
64Kernel::Logger g_log("PseudoVoigt");
65} // namespace
66
67DECLARE_FUNCTION(PseudoVoigt)
68
69
73void PseudoVoigt::init() {
74 declareParameter("Mixing", 0.5);
75 declareParameter("Intensity", 1.);
76 declareParameter("PeakCentre", 0.);
77 declareParameter("FWHM", 1.);
78
79 auto mixingConstraint = std::make_unique<BoundaryConstraint>(this, "Mixing", 0.0, 1.0, true);
80 mixingConstraint->setPenaltyFactor(1e9);
81 addConstraint(std::move(mixingConstraint));
82
83 auto fwhm_constraint = std::make_unique<BoundaryConstraint>(this, "FWHM", 1.E-20, true);
84 fwhm_constraint->setPenaltyFactor(1e9);
85 addConstraint(std::move(fwhm_constraint));
86
87 // init the peak height setup parameters
88 m_height = 1.; // peak height set by user
89
90 // parameter set history: all start from an arbitary out of boundary value
91 // (100 as easy). if set, order set to 0
92 m_set_history_distances.resize(4, 128);
93 // 0: mixing, 1: intensity, 2: fwhm, 3: height
94}
95
101void PseudoVoigt::functionLocal(double *out, const double *xValues, const size_t nData) const {
102 // read values
103 const double peak_intensity = getParameter("Intensity");
104 const double x0 = getParameter("PeakCentre");
105 const double gamma = fabs(getParameter("FWHM"));
106 const double gFraction = getParameter("Mixing");
107
108 if (gamma < 1.E-20)
109 throw std::runtime_error("Pseudo-voigt has FWHM as 0. It will generate "
110 "infinity at center in the Lorentzian part.");
111
112 const double lFraction = 1.0 - gFraction;
113
114 // calculate constants
115 const double a_g = cal_ag(gamma);
116 const double b_g = cal_bg(gamma);
117 const double gamma_div_2 = 0.5 * gamma;
118 const double gammasq_div_4 = gamma_div_2 * gamma_div_2;
119
120 for (size_t i = 0; i < nData; ++i) {
121 double xDiffSquared = (xValues[i] - x0) * (xValues[i] - x0);
122 out[i] = peak_intensity * (gFraction * cal_gaussian(a_g, b_g, xDiffSquared) +
123 lFraction * cal_lorentzian(gamma_div_2, gammasq_div_4, xDiffSquared));
124 }
125}
126
132void PseudoVoigt::functionDerivLocal(Jacobian *out, const double *xValues, const size_t nData) {
133 // get parameters
134 double peak_intensity = getParameter("Intensity");
135 double x0 = getParameter("PeakCentre");
136 double gamma = getParameter("FWHM");
137 double gFraction = getParameter("Mixing");
138 double lFraction = 1.0 - gFraction;
139
140 // calcualte constants
141 const double a_g = cal_ag(gamma);
142 const double b_g = cal_bg(gamma);
143 const double gamma_div_2 = 0.5 * gamma;
144 const double gammasq_div_4 = gamma_div_2 * gamma_div_2;
145
146 // derivatives
147 for (size_t i = 0; i < nData; ++i) {
148 double xDiff = (xValues[i] - x0);
149 double xDiffSquared = xDiff * xDiff;
150
151 // calculate normalized Gaussian and Lorentzian
152 double gaussian_term = cal_gaussian(a_g, b_g, xDiffSquared);
153 double lorentzian_term = cal_lorentzian(gamma_div_2, gammasq_div_4, xDiffSquared);
154
155 // derivative to mixing/eta (0-th)
156 out->set(i, 0, peak_intensity * (gaussian_term - lorentzian_term));
157
158 // derivative on intensity (1-st)
159 out->set(i, 1, gFraction * gaussian_term + lFraction * lorentzian_term);
160
161 // peak center: x0
162 const double derive_g_x0 = 2. * b_g * xDiff * gaussian_term;
163 const double derive_l_x0 = 4. * M_PI * xDiff / gamma * lorentzian_term * lorentzian_term;
164 const double deriv_x0 = peak_intensity * (gFraction * derive_g_x0 + lFraction * derive_l_x0);
165 out->set(i, 2, deriv_x0);
166
167 // peak width: gamma or H
168 const double t1 = -1. / gamma * gaussian_term;
169 const double t2 = 2. * b_g * xDiffSquared * gaussian_term / gamma;
170 const double t3 = lorentzian_term / gamma;
171 const double t4 = -M_PI * lorentzian_term * lorentzian_term;
172 const double derive_gamma = peak_intensity * (gFraction * (t1 + t2) + lFraction * (t3 + t4));
173 out->set(i, 3, derive_gamma);
174 }
175}
176
178void PseudoVoigt::setParameter(const std::string &name, const double &value, bool explicitlySet) {
179 g_log.debug() << "[PV] Set " << name << " as " << value << "\n";
181}
182
200void PseudoVoigt::setParameter(size_t i, const double &value, bool explicitlySet) {
201 API::IPeakFunction::setParameter(i, value, explicitlySet);
202
203 g_log.debug() << "[PV] Set " << i << "-th parameter with value " << value << "\n";
204
205 // explicitly set means that there is a chance that some parameter shall be
206 // re-calculated
207 // peak center shall be excluded as it does nothing to do with height, H,
208 // intensity and mixing
209 if (explicitlySet && i != 2) {
210 g_log.debug() << "Update set history for " << i << "-th parameter"
211 << "\n";
214 }
215
216 return;
217}
218
220 // peak center
221 size_t to_calculate_index = get_parameter_to_calculate_from_set();
222 g_log.debug() << "Time to calculate " << to_calculate_index << "-th parameter"
223 << "\n";
224
225 bool some_value_updated{true};
226 if (to_calculate_index == 0) {
227 // calculate mixings
228 double peak_intensity = getParameter(1);
229 double gamma = getParameter(3);
230 double mixing = (m_height * 0.5 * M_PI * gamma / peak_intensity - 1) / (sqrt(M_PI * M_LN2) - 1);
231 if (mixing < 0) {
232 g_log.debug() << "Calculated mixing (eta) = " << mixing << " < 0. Set to 0 instead"
233 << "\n";
234 mixing = 0;
235 } else if (mixing > 1) {
236 g_log.debug() << "Calculated mixing (eta) = " << mixing << " > 1. Set to 1 instead"
237 << "\n";
238 mixing = 1;
239 }
240
241 setParameter(0, mixing,
242 false); // no explicitly set: won't cause further re-calcualting
243 } else if (to_calculate_index == 1) {
244 // calculate intensity
245 double eta = getParameter(0);
246 double gamma = getParameter(3);
247 double peakIntensity = m_height / 2. / (1 + (sqrt(M_PI * M_LN2) - 1) * eta) * (M_PI * gamma);
248 setParameter(1, peakIntensity, false);
249 g_log.debug() << "Estimate peak intensity = " << peakIntensity << "\n";
250 } else if (to_calculate_index == 3) {
251 // calculate peak width
252 double eta = getParameter(0);
253 double peakIntensity = getParameter(1);
254
255 g_log.debug() << "Intensity = " << peakIntensity << ", height = " << m_height << ", mixing = " << eta << "\n";
256 double gamma = peakIntensity * 2 * (1 + (sqrt(M_PI * M_LN2) - 1) * eta) / (M_PI * m_height);
257 if (gamma < 1.E-10) {
258 g_log.debug() << "Peak width (H or Gamma) = " << gamma << ". Set 1.E-2 instead"
259 << "\n";
260 }
261 setParameter(3, gamma, false);
262 } else {
263 // no value updated
264 some_value_updated = false;
265 }
266
267 return some_value_updated;
268}
269
275void PseudoVoigt::update_set_history(size_t set_index) {
276 if (set_index > 3)
277 throw std::runtime_error("Parameter set up history index out of boundary");
278
279 size_t prev_distance = m_set_history_distances[set_index];
280 for (size_t i = 0; i < 4; ++i) {
281 // no operation on any never-set or out-dated index
282 if (m_set_history_distances[i] >= 3)
283 continue;
284
285 // only increase the historoy distance by 1 in case
286 // the other parameters with those with distance less than the previous one
287 // i.e., out of date or never been set
288 if (m_set_history_distances[i] < prev_distance) {
290 // if out of range, don't worry about it
291 }
292 }
293
294 // set the current one to 0 (most recent)
295 m_set_history_distances[set_index] = 0;
296}
297
305 // go through setting-history-distance
306 size_t index_largest_distance{0};
307 size_t largest_distance{0};
308 size_t num_been_set{0};
309
310 for (size_t i = 0; i < m_set_history_distances.size(); ++i) {
311 g_log.debug() << "distance[" << i << "] = " << m_set_history_distances[i] << "\n";
312 if (m_set_history_distances[i] >= largest_distance) {
313 // for those to set for: any number counts
314 index_largest_distance = i;
315 largest_distance = m_set_history_distances[i];
316 }
317 // only those been set and not out of date counted
318 if (m_set_history_distances[i] < 3) {
319 ++num_been_set;
320 }
321 }
322
323 // signal for no enough index been set up
324 if (num_been_set < 3)
325 index_largest_distance = 128;
326
327 return index_largest_distance;
328}
329
333double PseudoVoigt::height() const {
334 double peak_intensity = getParameter("Intensity");
335 double gamma = getParameter("FWHM");
336 double eta = getParameter("Mixing");
337 const double height = 2. * peak_intensity * (1 + (sqrt(M_PI * M_LN2) - 1) * eta) / (M_PI * gamma);
338
339 return height;
340}
341
346void PseudoVoigt::setHeight(const double h) {
347 // set height
348 double prev_height = m_height;
349 m_height = h;
350
351 // update set history
353 g_log.debug() << "PV: set height = " << m_height << "\n";
354
355 // update other parameters' value
356 bool updated_any_value = estimate_parameter_value();
357 if (!updated_any_value) {
358 // force to update peak intensity
359 double old_intensity = getParameter("Intensity");
360 // This is what Roman suggests!
361 double new_intensity{1.};
362 if (prev_height > 0)
363 new_intensity = old_intensity * m_height / prev_height;
364 else
365 new_intensity = m_height; // this is a better than nothing estimation
366 setParameter("Intensity", new_intensity,
367 false); // no need to update other parameters
368 }
369}
370
374void PseudoVoigt::setFwhm(const double w) { setParameter("FWHM", w, true); }
375
376} // namespace Mantid::CurveFitting::Functions
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
#define DECLARE_FUNCTION(classname)
Macro for declaring a new type of function to be used with the FunctionFactory.
#define fabs(x)
Definition Matrix.cpp:22
void setParameter(const std::string &name, const double &value, bool explicitlySet=true) override
Override parent so that we may bust the cache when a parameter is set.
Represents the Jacobian in IFitFunction::functionDeriv.
Definition Jacobian.h:22
virtual void set(size_t iY, size_t iP, double value)=0
Set a value to a Jacobian matrix element.
double getParameter(size_t i) const override
Get i-th parameter.
void setFwhm(const double w) override
set FWHM
void functionLocal(double *out, const double *xValues, const size_t nData) const override
calculate pseudo voigt
void functionDerivLocal(API::Jacobian *out, const double *xValues, const size_t nData) override
calcualte derivative analytically
void setParameter(size_t i, const double &value, bool explicitlySet=true) override
Set i-th parameter.
void setHeight(const double h) override
set non-native parameter peak height
double height() const override
calculate effective parameter: height
size_t get_parameter_to_calculate_from_set()
get the parameter (by index) to calculate according to parameter set history
void update_set_history(size_t set_index)
The purpose of this is to track which user-specified parameter is outdated and to be calculated.
std::string name() const override
Returns the function's name.
Definition PseudoVoigt.h:48
std::vector< size_t > m_set_history_distances
historty of the order to be set
Definition PseudoVoigt.h:65
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
Kernel::Logger g_log("ExperimentInfo")
static logger object
STL namespace.