Mantid
Loading...
Searching...
No Matches
IkedaCarpenterPV.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"
23#include "MantidKernel/Logger.h"
25
26#include <cmath>
27#include <gsl/gsl_math.h>
28#include <gsl/gsl_multifit_nlin.h>
29#include <gsl/gsl_sf_erf.h>
30#include <limits>
31
33
34using namespace CurveFitting;
35
36namespace {
38Kernel::Logger g_log("IkedaCarpenterPV");
39} // namespace
40
41using namespace Kernel;
42
43using namespace CurveFitting::SpecialFunctionSupport;
44
45using namespace Geometry;
46using namespace Constraints;
47
48DECLARE_FUNCTION(IkedaCarpenterPV)
49
50double IkedaCarpenterPV::centre() const { return getParameter("X0"); }
51
52void IkedaCarpenterPV::setHeight(const double h) {
53 // calculate height of peakshape function corresponding to intensity = 1
54 setParameter("I", 1);
55 double h0 = height();
56
57 // to avoid devide by zero and to insane value for I to be set
58 double minCutOff = 100.0 * std::numeric_limits<double>::min();
59 if (h0 > 0 && h0 < minCutOff)
60 h0 = minCutOff;
61 if (h0 < 0 && h0 > -minCutOff)
62 h0 = -minCutOff;
63
64 // The intensity is then estimated to be h/h0
65 setParameter("I", h / h0);
66}
67
69 // return the function value at centre()
70 // using arrays - otherwise coverity warning
71 double h0[1];
72 double toCentre[1];
73 toCentre[0] = centre();
74 constFunction(h0, toCentre, 1);
75 return h0[0];
76}
77
78double IkedaCarpenterPV::fwhm() const {
79 double sigmaSquared = getParameter("SigmaSquared");
80 double gamma = getParameter("Gamma");
81
82 if (sigmaSquared < 0) {
83 g_log.debug() << "SigmaSquared NEGATIVE!.\n"
84 << "Likely due to a fit not converging properly\n"
85 << "If this is frequent problem please report to Mantid team.\n"
86 << "For now to calculate width force SigmaSquared positive.\n";
87 sigmaSquared = -sigmaSquared;
88 }
89 if (gamma < 0) {
90 g_log.debug() << "Gamma NEGATIVE!.\n"
91 << "Likely due to a fit not converging properly\n"
92 << "If this is frequent problem please report to Mantid team.\n"
93 << "For now to calculate width force Gamma positive.\n";
94 gamma = -gamma;
95 }
96
97 // Use the same Thompson-Cox-Hastings approximation that the peak shape
98 // itself uses (via convertVoigtToPseudo) so that the reported FWHM is
99 // consistent with the actual profile width.
100 double H = 1.0, eta = 0.5;
101 convertVoigtToPseudo(sigmaSquared, gamma, H, eta);
102 return H;
103}
104
105void IkedaCarpenterPV::setFwhm(const double w) {
106 setParameter("SigmaSquared", w * w / (32.0 * M_LN2)); // used 4.0 * 8.0 = 32.0
107 setParameter("Gamma", w / 2.0);
108}
109
110void IkedaCarpenterPV::setCentre(const double c) { setParameter("X0", c); }
111
113 declareParameter("I", 0.0,
114 "The integrated intensity of the peak. I.e. "
115 "approximately equal to HWHM times height of "
116 "peak");
117 this->lowerConstraint0("I");
118 declareParameter("Alpha0", 1.6, "Used to model fast decay constant");
119 this->lowerConstraint0("Alpha0");
120 declareParameter("Alpha1", 1.5, "Used to model fast decay constant");
121 this->lowerConstraint0("Alpha1");
122 declareParameter("Beta0", 31.9, "Inverse of slow decay constant");
123 this->lowerConstraint0("Beta0");
124 declareParameter("Kappa", 46.0, "Controls contribution of slow decay term");
125 this->lowerConstraint0("Kappa");
126 declareParameter("SigmaSquared", 1.0, "standard deviation squared (Voigt Guassian broadening)");
127 this->lowerConstraint0("SigmaSquared");
128 declareParameter("Gamma", 1.0, "Voigt Lorentzian broadening");
129 this->lowerConstraint0("Gamma");
130 declareParameter("X0", 0.0, "Peak position");
131 this->lowerConstraint0("X0");
132}
133
134void IkedaCarpenterPV::lowerConstraint0(const std::string &paramName) {
135 auto mixingConstraint = std::make_unique<BoundaryConstraint>(this, paramName, 0.0, true);
136 mixingConstraint->setPenaltyFactor(1e9);
137
138 addConstraint(std::move(mixingConstraint));
139}
140
149void IkedaCarpenterPV::calWavelengthAtEachDataPoint(const double *xValues, const size_t &nData) const {
150 // if wavelength vector already have the right size no need for resizing it
151 // further we make the assumption that no need to recalculate this vector if
152 // it already has the right size
153
154 if (m_waveLength.size() != nData) {
155 m_waveLength.resize(nData);
156
157 Mantid::Kernel::Unit_sptr wavelength = Mantid::Kernel::UnitFactory::Instance().create("Wavelength");
158 for (size_t i = 0; i < nData; i++) {
159 m_waveLength[i] = xValues[i];
160 }
161
162 // note if a version of convertValue was added which allows a double* as
163 // first argument then could avoid copying above plus only have to resize
164 // m_wavelength when its size smaller than nData
166 // Single-point evaluations (nData == 1) are shape probes coming from
167 // height(), IPeakFunction::getDomainInterval()'s findBound, or the
168 // PeakFunctionIntegrator's GSL wrapper. They are routinely invoked --
169 // including via getCentreParameterName() / intensity() / setIntensity() --
170 // on freshly-constructed peak functions that have no workspace attached
171 // yet, and the wavelength default is harmless for a 1-point probe. The
172 // missing-workspace warnings are demoted to debug in that case so they
173 // don't drown the log; full-spectrum evaluations (nData > 1) keep the
174 // warning so a genuinely missing workspace during fitting is still flagged.
175 const bool singlePointProbe = (nData == 1);
176 if (mws) {
177 Instrument_const_sptr instrument = mws->getInstrument();
178 Geometry::IComponent_const_sptr sample = instrument->getSample();
179 if (sample != nullptr) {
180 convertValue(m_waveLength, wavelength, mws, m_workspaceIndex);
181 } else {
182 auto &noSample = singlePointProbe ? g_log.debug() : g_log.warning();
183 noSample << "No sample set for instrument in workspace.\n"
184 << "Can't calculate wavelength in IkedaCarpenter.\n"
185 << "Default all wavelengths to one.\n"
186 << "Solution is to load appropriate instrument into workspace.\n";
187 for (size_t i = 0; i < nData; i++)
188 m_waveLength[i] = 1.0;
189 }
190 } else {
191 auto &noWs = singlePointProbe ? g_log.debug() : g_log.warning();
192 noWs << "Workspace not set.\n"
193 << "Can't calculate wavelength in IkedaCarpenter.\n"
194 << "Default all wavelengths to one.\n"
195 << "Solution call setMatrixWorkspace() for function.\n";
196 for (size_t i = 0; i < nData; i++)
197 m_waveLength[i] = 1.0;
198 }
199 }
200}
201
209void IkedaCarpenterPV::convertVoigtToPseudo(const double &voigtSigmaSq, const double &voigtGamma, double &H,
210 double &eta) const {
211 double fwhmGsq = 8.0 * M_LN2 * voigtSigmaSq;
212 double fwhmG = sqrt(fwhmGsq);
213 double fwhmG4 = fwhmGsq * fwhmGsq;
214 double fwhmL = voigtGamma;
215 double fwhmLsq = voigtGamma * voigtGamma;
216 double fwhmL4 = fwhmLsq * fwhmLsq;
217
218 H = pow(fwhmG4 * fwhmG + 2.69269 * fwhmG4 * fwhmL + 2.42843 * fwhmGsq * fwhmG * fwhmLsq +
219 4.47163 * fwhmGsq * fwhmLsq * fwhmL + 0.07842 * fwhmG * fwhmL4 + fwhmL4 * fwhmL,
220 0.2);
221
222 if (H == 0.0)
223 H = std::numeric_limits<double>::epsilon() * 1000.0;
224
225 double tmp = fwhmL / H;
226
227 eta = 1.36603 * tmp - 0.47719 * tmp * tmp + 0.11116 * tmp * tmp * tmp;
228}
229
230void IkedaCarpenterPV::constFunction(double *out, const double *xValues, const int &nData) const {
231 // Save the wavelength cache so that single-point evaluations (e.g. from
232 // height()) do not invalidate the full-spectrum cache used by functionLocal.
233 auto savedWaveLength = std::move(m_waveLength);
234 m_waveLength.clear();
235 try {
236 functionLocal(out, xValues, static_cast<size_t>(nData));
237 } catch (...) {
238 m_waveLength = std::move(savedWaveLength);
239 throw;
240 }
241 m_waveLength = std::move(savedWaveLength);
242}
243
244void IkedaCarpenterPV::functionLocal(double *out, const double *xValues, const size_t nData) const {
245 const double I = getParameter("I");
246 const double alpha0 = getParameter("Alpha0");
247 const double alpha1 = getParameter("Alpha1");
248 const double beta0 = getParameter("Beta0");
249 const double kappa = getParameter("Kappa");
250 const double voigtsigmaSquared = getParameter("SigmaSquared");
251 const double voigtgamma = getParameter("Gamma");
252 const double X0 = getParameter("X0");
253
254 // cal pseudo voigt sigmaSq and gamma and eta
255 double gamma = 1.0; // dummy initialization
256 double eta = 0.5; // dummy initialization
257 convertVoigtToPseudo(voigtsigmaSquared, voigtgamma, gamma, eta);
258 const double eps = std::numeric_limits<double>::epsilon() * 1000.0;
259 double sigmaSquared = std::max(gamma * gamma / (8.0 * M_LN2), eps); // pseudo voigt sigma^2
260
261 const double beta = 1 / beta0;
262
263 // equations taken from Fullprof manual
264
265 const double k = 0.05;
266
267 const double someConst = 1.0 / sqrt(2.0 * sigmaSquared);
268
269 // update wavelength vector
270 calWavelengthAtEachDataPoint(xValues, nData);
271
272 for (size_t i = 0; i < nData; i++) {
273 double diff = xValues[i] - X0;
274
275 double R = exp(-81.799 / (m_waveLength[i] * m_waveLength[i] * kappa));
276 double alpha = 1.0 / (alpha0 + m_waveLength[i] * alpha1);
277
278 double a_minus = alpha * (1 - k);
279 double a_plus = alpha * (1 + k);
280 double x = a_minus - beta;
281 double y = alpha - beta;
282 double z = a_plus - beta;
283
284 double Nu = 1 - R * a_minus / x;
285 double Nv = 1 - R * a_plus / z;
286 double Ns = -2 * (1 - R * alpha / y);
287 double Nr = 2 * R * alpha * alpha * beta * k * k / (x * y * z);
288
289 double u = a_minus * (a_minus * sigmaSquared - 2 * diff) / 2.0;
290 double v = a_plus * (a_plus * sigmaSquared - 2 * diff) / 2.0;
291 double s = alpha * (alpha * sigmaSquared - 2 * diff) / 2.0;
292 double r = beta * (beta * sigmaSquared - 2 * diff) / 2.0;
293
294 double yu = (a_minus * sigmaSquared - diff) * someConst;
295 double yv = (a_plus * sigmaSquared - diff) * someConst;
296 double ys = (alpha * sigmaSquared - diff) * someConst;
297 double yr = (beta * sigmaSquared - diff) * someConst;
298
299 std::complex<double> zs = std::complex<double>(-alpha * diff, 0.5 * alpha * gamma);
300 std::complex<double> zu = (1 - k) * zs;
301 std::complex<double> zv = (1 + k) * zs;
302 std::complex<double> zr = std::complex<double>(-beta * diff, 0.5 * beta * gamma);
303
304 double N = 0.25 * alpha * (1 - k * k) / (k * k);
305
306 out[i] = I * N *
307 ((1 - eta) * (Nu * exp(u + gsl_sf_log_erfc(yu)) + Nv * exp(v + gsl_sf_log_erfc(yv)) +
308 Ns * exp(s + gsl_sf_log_erfc(ys)) + Nr * exp(r + gsl_sf_log_erfc(yr))) -
309 eta * 2.0 / M_PI *
310 (Nu * exponentialIntegral(zu).imag() + Nv * exponentialIntegral(zv).imag() +
311 Ns * exponentialIntegral(zs).imag() + Nr * exponentialIntegral(zr).imag()));
312 }
313}
314
315void IkedaCarpenterPV::functionDerivLocal(API::Jacobian *jacobian, const double *xValues, const size_t nData) {
316 // Parameter order: 0:I 1:Alpha0 2:Alpha1 3:Beta0 4:Kappa 5:SigmaSquared 6:Gamma 7:X0
317 // For more details on the origin of this derivative see
318 // docs/source/fitting/fitfunctions/IkedaCarpenterPV_analytical_derivative.rst
319
320 const double I = getParameter("I");
321 const double alpha0 = getParameter("Alpha0");
322 const double alpha1 = getParameter("Alpha1");
323 const double beta0 = getParameter("Beta0");
324 const double kappa = getParameter("Kappa");
325 const double voigtSigmaSq = getParameter("SigmaSquared");
326 const double voigtGamma = getParameter("Gamma");
327 const double X0 = getParameter("X0");
328
329 double H = 1.0, eta = 0.5;
330 convertVoigtToPseudo(voigtSigmaSq, voigtGamma, H, eta);
331
332 const double eps = std::numeric_limits<double>::epsilon() * 1000.0;
333 const double Hsafe = std::max(H, eps);
334 const double Hsafe2 = Hsafe * Hsafe;
335 const double S = std::max(Hsafe2 / (8.0 * M_LN2), eps);
336 const double beta = 1.0 / beta0;
337 const double beta2 = beta * beta;
338
339 constexpr double k = 0.05;
340 constexpr double kk = k * k;
341 constexpr double one_minus_k = 1.0 - k;
342 constexpr double one_plus_k = 1.0 + k;
343 const double one_minus_eta = 1.0 - eta;
344 constexpr double twoOverPi = 2.0 / M_PI;
345 const double etaTwoOverPi = eta * twoOverPi;
346 const double halfHsafe = 0.5 * Hsafe;
347 const double halfH2 = halfHsafe * halfHsafe;
348
349 const double invRoot2S = 1.0 / std::sqrt(2.0 * S);
350 const double rootSOver2 = std::sqrt(S / 2.0);
351 const double twoInvSqrtPi = 2.0 / std::sqrt(M_PI);
352 const double CexpSFactor = invRoot2S / (2.0 * S); // = 1/(2*S*sqrt(2S)), for dK/dS
353
354 // Voigt -> pseudo-Voigt chain derivatives
355 const double g2 = std::max(8.0 * M_LN2 * voigtSigmaSq, eps);
356 const double g = std::sqrt(g2);
357 const double g3 = g2 * g;
358 const double g4 = g2 * g2;
359
360 const double L = voigtGamma;
361 const double L2 = L * L;
362 const double L3 = L2 * L;
363 const double L4 = L2 * L2;
364
365 constexpr double c1 = 2.69269, c2 = 2.42843, c3 = 4.47163, c4 = 0.07842;
366
367 const double H4 = Hsafe2 * Hsafe2;
368 const double inv5H4 = 1.0 / (5.0 * std::max(H4, eps));
369
370 const double dPdg = 5.0 * g4 + 4.0 * c1 * g3 * L + 3.0 * c2 * g2 * L2 + 2.0 * c3 * g * L3 + c4 * L4;
371 const double dPdL = c1 * g4 + 2.0 * c2 * g3 * L + 3.0 * c3 * g2 * L2 + 4.0 * c4 * g * L3 + 5.0 * L4;
372
373 const double dgdV = (4.0 * M_LN2) / g;
374 const double dHdV = dPdg * dgdV * inv5H4;
375 const double dHdL = dPdL * inv5H4;
376
377 const double t = L / Hsafe;
378 const double deta_dt = 1.36603 - 0.95438 * t + 0.33348 * t * t;
379 const double invHsafe2 = 1.0 / Hsafe2;
380
381 const double deta_dV = deta_dt * (-L * dHdV * invHsafe2);
382 const double deta_dL = deta_dt * (1.0 / Hsafe - L * dHdL * invHsafe2);
383
384 const double inv4Ln2 = 1.0 / (4.0 * M_LN2);
385 const double dSdV = Hsafe * dHdV * inv4Ln2;
386 const double dSdL = Hsafe * dHdL * inv4Ln2;
387
388 const double kappa2_inv = 1.0 / (kappa * kappa);
389
390 calWavelengthAtEachDataPoint(xValues, nData);
391
392 for (size_t i = 0; i < nData; ++i) {
393 const double lambda = m_waveLength[i];
394 const double lambda2 = lambda * lambda;
395 const double diff = xValues[i] - X0;
396 const double diff2 = diff * diff;
397
398 const double R = std::exp(-81.799 / (lambda2 * kappa));
399 const double alpha = 1.0 / (alpha0 + lambda * alpha1);
400 const double alpha2 = alpha * alpha;
401
402 const double a_minus = alpha * one_minus_k;
403 const double a_plus = alpha * one_plus_k;
404
405 const double x = a_minus - beta;
406 const double y = alpha - beta;
407 const double z = a_plus - beta;
408
409 const double inv_x = 1.0 / x;
410 const double inv_y = 1.0 / y;
411 const double inv_z = 1.0 / z;
412 const double inv_x2 = inv_x * inv_x;
413 const double inv_y2 = inv_y * inv_y;
414 const double inv_z2 = inv_z * inv_z;
415
416 const double Nu = 1.0 - R * a_minus * inv_x;
417 const double Nv = 1.0 - R * a_plus * inv_z;
418 const double Ns = -2.0 * (1.0 - R * alpha * inv_y);
419 const double Nr = 2.0 * R * alpha2 * beta * kk * inv_x * inv_y * inv_z;
420
421 // Gaussian kernel values: K = exp(u + log_erfc(y))
422 const double amuS_d = a_minus * S - diff; // a_minus*S - diff (reused in dK)
423 const double apuS_d = a_plus * S - diff;
424 const double alS_d = alpha * S - diff;
425 const double beS_d = beta * S - diff;
426
427 const double u = 0.5 * a_minus * (amuS_d - diff);
428 const double v = 0.5 * a_plus * (apuS_d - diff);
429 const double s = 0.5 * alpha * (alS_d - diff);
430 const double r = 0.5 * beta * (beS_d - diff);
431
432 const double yu = amuS_d * invRoot2S;
433 const double yv = apuS_d * invRoot2S;
434 const double ys = alS_d * invRoot2S;
435 const double yr = beS_d * invRoot2S;
436
437 const double Ku = std::exp(u + gsl_sf_log_erfc(yu));
438 const double Kv = std::exp(v + gsl_sf_log_erfc(yv));
439 const double Ks = std::exp(s + gsl_sf_log_erfc(ys));
440 const double Kr = std::exp(r + gsl_sf_log_erfc(yr));
441
442 // Lorentzian kernel values
443 const std::complex<double> zu(-a_minus * diff, a_minus * halfHsafe);
444 const std::complex<double> zv(-a_plus * diff, a_plus * halfHsafe);
445 const std::complex<double> zs(-alpha * diff, alpha * halfHsafe);
446 const std::complex<double> zr(-beta * diff, beta * halfHsafe);
447
448 const std::complex<double> Fu = exponentialIntegral(zu);
449 const std::complex<double> Fv = exponentialIntegral(zv);
450 const std::complex<double> Fs = exponentialIntegral(zs);
451 const std::complex<double> Fr = exponentialIntegral(zr);
452
453 const double Ju = Fu.imag();
454 const double Jv = Fv.imag();
455 const double Js = Fs.imag();
456 const double Jr = Fr.imag();
457
458 // F'(z) = F(z) - 1/z, decomposed into real/imag to avoid complex division.
459 // For z = (-a*diff, a*halfH): 1/z = (-diff, -halfH) / (a*(diff^2+halfH^2))
460 // So F'_r = F_r + diff/(a*d2h2), F'_i = F_i + halfH/(a*d2h2)
461 const double inv_d2h2 = 1.0 / (diff2 + halfH2);
462 const double d_term = diff * inv_d2h2; // diff / (diff^2 + halfH^2)
463 const double h_term = halfHsafe * inv_d2h2; // halfH / (diff^2 + halfH^2)
464
465 const double Fpu_r = Fu.real() + d_term / a_minus;
466 const double Fpu_i = Fu.imag() + h_term / a_minus;
467 const double Fpv_r = Fv.real() + d_term / a_plus;
468 const double Fpv_i = Fv.imag() + h_term / a_plus;
469 const double Fps_r = Fs.real() + d_term / alpha;
470 const double Fps_i = Fs.imag() + h_term / alpha;
471 const double Fpr_r = Fr.real() + d_term / beta;
472 const double Fpr_i = Fr.imag() + h_term / beta;
473
474 // Function value components
475 const double Q = 0.25 * alpha * (1.0 - kk) / kk;
476 const double IQ = I * Q;
477
478 const double G = Nu * Ku + Nv * Kv + Ns * Ks + Nr * Kr;
479 const double Lmix = Nu * Ju + Nv * Jv + Ns * Js + Nr * Jr;
480 const double modelCore = one_minus_eta * G - etaTwoOverPi * Lmix;
481
482 // Common Gaussian derivative factor: (2/sqrt(pi)) * exp(-diff^2/(2S))
483 const double Cexp = twoInvSqrtPi * std::exp(-diff2 / (2.0 * S));
484 const double CexpRS2 = Cexp * rootSOver2; // for dK/da
485
486 // -- Inlined K kernel derivatives --
487 // dK/da = (a*S - diff)*K - Cexp*sqrt(S/2)
488 const double dKu_da = amuS_d * Ku - CexpRS2;
489 const double dKv_da = apuS_d * Kv - CexpRS2;
490 const double dKs_da = alS_d * Ks - CexpRS2;
491 const double dKr_da = beS_d * Kr - CexpRS2;
492
493 // dK/dS = 0.5*a^2*K - Cexp*(a*S+diff)/(2*S*sqrt(2S))
494 const double dKu_dS = 0.5 * a_minus * a_minus * Ku - Cexp * (a_minus * S + diff) * CexpSFactor;
495 const double dKv_dS = 0.5 * a_plus * a_plus * Kv - Cexp * (a_plus * S + diff) * CexpSFactor;
496 const double dKs_dS = 0.5 * alpha2 * Ks - Cexp * (alpha * S + diff) * CexpSFactor;
497 const double dKr_dS = 0.5 * beta2 * Kr - Cexp * (beta * S + diff) * CexpSFactor;
498
499 // dK/dd = -a*K + Cexp/sqrt(2S)
500 const double CexpIR2S = Cexp * invRoot2S;
501 const double dKu_dd = -a_minus * Ku + CexpIR2S;
502 const double dKv_dd = -a_plus * Kv + CexpIR2S;
503 const double dKs_dd = -alpha * Ks + CexpIR2S;
504 const double dKr_dd = -beta * Kr + CexpIR2S;
505
506 // -- J kernel derivatives --
507 // dJ/da = Im(Fp * (-diff, halfH)) = Fp_r*halfH - Fp_i*diff
508 const double dJu_da = Fpu_r * halfHsafe - Fpu_i * diff;
509 const double dJv_da = Fpv_r * halfHsafe - Fpv_i * diff;
510 const double dJs_da = Fps_r * halfHsafe - Fps_i * diff;
511 const double dJr_da = Fpr_r * halfHsafe - Fpr_i * diff;
512
513 // dJ/dH = 0.5*a*Re(Fp)
514 const double dJu_dH = 0.5 * a_minus * Fpu_r;
515 const double dJv_dH = 0.5 * a_plus * Fpv_r;
516 const double dJs_dH = 0.5 * alpha * Fps_r;
517 const double dJr_dH = 0.5 * beta * Fpr_r;
518
519 // dJ/dd = -a*Im(Fp)
520 const double dJu_dd = -a_minus * Fpu_i;
521 const double dJv_dd = -a_plus * Fpv_i;
522 const double dJs_dd = -alpha * Fps_i;
523 const double dJr_dd = -beta * Fpr_i;
524
525 // -- Coefficient derivatives wrt alpha, beta, R --
526 const double Rbeta = R * beta;
527 const double dNu_dalpha = Rbeta * one_minus_k * inv_x2;
528 const double dNv_dalpha = Rbeta * one_plus_k * inv_z2;
529 const double dNs_dalpha = -2.0 * Rbeta * inv_y2;
530 const double dNr_dalpha = Nr * (2.0 / alpha - one_minus_k * inv_x - inv_y - one_plus_k * inv_z);
531
532 const double dNu_dbeta = -R * a_minus * inv_x2;
533 const double dNv_dbeta = -R * a_plus * inv_z2;
534 const double dNs_dbeta = 2.0 * R * alpha * inv_y2;
535 const double dNr_dbeta = Nr * (1.0 / beta + inv_x + inv_y + inv_z);
536
537 const double dNu_dR = -a_minus * inv_x;
538 const double dNv_dR = -a_plus * inv_z;
539 const double dNs_dR = 2.0 * alpha * inv_y;
540 const double dNr_dR = 2.0 * alpha2 * beta * kk * inv_x * inv_y * inv_z;
541
542 // -- Mix derivatives --
543 // dG/dalpha: Kr doesn't depend on alpha, so dKr_dalpha term is zero
544 const double dG_dalpha = dNu_dalpha * Ku + Nu * one_minus_k * dKu_da + dNv_dalpha * Kv + Nv * one_plus_k * dKv_da +
545 dNs_dalpha * Ks + Ns * dKs_da + dNr_dalpha * Kr;
546
547 const double dL_dalpha = dNu_dalpha * Ju + Nu * one_minus_k * dJu_da + dNv_dalpha * Jv + Nv * one_plus_k * dJv_da +
548 dNs_dalpha * Js + Ns * dJs_da + dNr_dalpha * Jr;
549
550 // dG/dbeta: Ku,Kv,Ks don't depend on beta, so only Nr*dKr_da survives
551 const double dG_dbeta = dNu_dbeta * Ku + dNv_dbeta * Kv + dNs_dbeta * Ks + dNr_dbeta * Kr + Nr * dKr_da;
552
553 const double dL_dbeta = dNu_dbeta * Ju + dNv_dbeta * Jv + dNs_dbeta * Js + dNr_dbeta * Jr + Nr * dJr_da;
554
555 // dG/dR, dL/dR: R doesn't affect kernel arguments, only coefficients
556 const double dG_dR = dNu_dR * Ku + dNv_dR * Kv + dNs_dR * Ks + dNr_dR * Kr;
557 const double dL_dR = dNu_dR * Ju + dNv_dR * Jv + dNs_dR * Js + dNr_dR * Jr;
558
559 // dG/dS (K depends on S; J does not, so dL/dS = 0)
560 const double dG_dS = Nu * dKu_dS + Nv * dKv_dS + Ns * dKs_dS + Nr * dKr_dS;
561
562 // dL/dH (J depends on H; K does not, so dG/dH = 0)
563 const double dL_dH = Nu * dJu_dH + Nv * dJv_dH + Ns * dJs_dH + Nr * dJr_dH;
564
565 // dG/dd, dL/dd
566 const double dG_dd = Nu * dKu_dd + Nv * dKv_dd + Ns * dKs_dd + Nr * dKr_dd;
567 const double dL_dd = Nu * dJu_dd + Nv * dJv_dd + Ns * dJs_dd + Nr * dJr_dd;
568
569 // -- Derivatives wrt natural variables (noting dG_dH=0, dL_dS=0) --
570 const double df_dI = Q * modelCore;
571 const double df_dalpha = I * (Q / alpha * modelCore + Q * (one_minus_eta * dG_dalpha - etaTwoOverPi * dL_dalpha));
572 const double df_dbeta = IQ * (one_minus_eta * dG_dbeta - etaTwoOverPi * dL_dbeta);
573 const double df_dR = IQ * (one_minus_eta * dG_dR - etaTwoOverPi * dL_dR);
574 const double df_dS = IQ * one_minus_eta * dG_dS; // dL_dS = 0
575 const double df_dH = -IQ * etaTwoOverPi * dL_dH; // dG_dH = 0
576 const double df_deta = -IQ * (G + twoOverPi * Lmix);
577 const double df_ddiff = IQ * (one_minus_eta * dG_dd - etaTwoOverPi * dL_dd);
578
579 // -- Chain rule to Mantid fit parameters --
580 const double neg_alpha2 = -alpha2;
581 jacobian->set(i, 0, df_dI);
582 jacobian->set(i, 1, df_dalpha * neg_alpha2);
583 jacobian->set(i, 2, df_dalpha * neg_alpha2 * lambda);
584 jacobian->set(i, 3, df_dbeta * (-beta2));
585 jacobian->set(i, 4, df_dR * R * 81.799 * kappa2_inv / lambda2);
586 jacobian->set(i, 5, df_dS * dSdV + df_dH * dHdV + df_deta * deta_dV);
587 jacobian->set(i, 6, df_dS * dSdL + df_dH * dHdL + df_deta * deta_dL);
588 jacobian->set(i, 7, -df_ddiff);
589 }
590}
591
593 const auto *domain1D = dynamic_cast<const API::FunctionDomain1D *>(&domain);
594 if (!domain1D) {
595 calNumericalDeriv(domain, jacobian);
596 return;
597 }
598 functionDerivLocal(&jacobian, domain1D->getPointerAt(0), domain1D->size());
599}
600
603 auto interval = getDomainInterval(1e-2);
604
606 API::IntegrationResult result = integrator.integrate(*this, interval.first, interval.second);
607
608 return result.result;
609}
610
611void IkedaCarpenterPV::setMatrixWorkspace(std::shared_ptr<const API::MatrixWorkspace> workspace, size_t wi,
612 double startX, double endX) {
614
615 // Invalidate cached wavelengths so they are recalculated on the next
616 // function evaluation using the newly-set workspace and workspace index.
617 m_waveLength.clear();
618
619 if (workspace) {
620 // If the instrument definition provided IC parameters the base-class call above handles
621 // the formula evaluation and unit conversion.
622 if (isExplicitlySet(parameterIndex("Alpha0")))
623 return;
624
625 // This next section handles conversion when no instrument defaults are present
626 // in such a scenario the generic defaults will be used, which are in TOF units.
627
628 // This will scale these default time-dependent IC parameters so the initial plot guess
629 // is reasonable for non-TOF workspaces (e.g. d-spacing, wavelength).
630
631 // Only applies to units with a simple power-law relationship with TOF;
632 // units like DeltaE have a non-linear, energy-dependent conversion that
633 // may be kinematically forbidden at the peak position.
634 const auto wsUnit = workspace->getAxis(0)->unit();
635 auto tof = Mantid::Kernel::UnitFactory::Instance().create("TOF");
636 double factor, power;
637 if (!wsUnit->quickConversion(*tof, factor, power))
638 return;
639
640 const auto peakCentre = getParameter("X0");
641 if (peakCentre != 0.0) {
642 const auto tofCentre = convertValue(peakCentre, tof, workspace, wi);
643 if (tofCentre != 0.0) {
644 const double scaleFactor = peakCentre / tofCentre;
645 if (std::abs(scaleFactor - 1.0) > 1e-6) {
646 const auto scaleIfDefault = [&](const std::string &name, const double factor) {
647 const size_t idx = parameterIndex(name);
648 if (!isExplicitlySet(idx))
649 setParameter(idx, getParameter(idx) * factor, false);
650 };
651 scaleIfDefault("Alpha0", scaleFactor);
652 scaleIfDefault("Alpha1", scaleFactor);
653 scaleIfDefault("Beta0", scaleFactor);
654 scaleIfDefault("SigmaSquared", scaleFactor * scaleFactor);
655 scaleIfDefault("Gamma", scaleFactor);
656 }
657 }
658 }
659 }
660}
661
662} // namespace Mantid::CurveFitting::Functions
const std::vector< double > * lambda
gsl_vector * tmp
double centre
#define DECLARE_FUNCTION(classname)
Macro for declaring a new type of function to be used with the FunctionFactory.
IPeaksWorkspace_sptr workspace
Represent a domain for functions of one real argument.
Base class that represents the domain of a function.
size_t m_workspaceIndex
An index to a spectrum.
Definition IFunctionMW.h:43
void setMatrixWorkspace(std::shared_ptr< const API::MatrixWorkspace > workspace, size_t wi, double startX, double endX) override
Set MatrixWorkspace.
std::shared_ptr< const API::MatrixWorkspace > getMatrixWorkspace() const
Get shared pointer to the workspace.
virtual double getParameter(size_t i) const =0
Get i-th parameter.
double convertValue(double value, Kernel::Unit_sptr &outUnit, const std::shared_ptr< const MatrixWorkspace > &ws, size_t wsIndex) const
Convert a value from one unit (inUnit) to unit defined in workspace (ws)
void calNumericalDeriv(const FunctionDomain &domain, Jacobian &jacobian)
Calculate numerical derivatives.
virtual size_t parameterIndex(const std::string &name) const =0
Returns the index of parameter name.
virtual void addConstraint(std::unique_ptr< IConstraint > ic)
Add a constraint to function.
virtual bool isExplicitlySet(size_t i) const =0
Checks if a parameter has been set explicitly.
virtual void declareParameter(const std::string &name, double initValue=0, const std::string &description="")=0
Declare a new parameter.
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.
virtual std::pair< double, double > getDomainInterval(double level=DEFAULT_SEARCH_LEVEL) const
Get the interval on which the peak has all its values above a certain level.
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.
IntegrationResult integrate(const IPeakFunction &peakFunction, double lowerLimit, double upperLimit) const
Integration of peak function on the interval [a, b].
Provide Ikeda-Carpenter-pseudo-Voigt peak shape function interface to IPeakFunction.
void functionLocal(double *out, const double *xValues, const size_t nData) const override
Function evaluation method to be implemented in the inherited classes.
void constFunction(double *out, const double *xValues, const int &nData) const
calculate the const function
void setHeight(const double h) override
Sets the parameters such that height == h.
void setCentre(const double c) override
Sets the parameters such that centre == c.
double intensity() const override
Returns the integral intensity of the peak.
double centre() const override
overwrite IPeakFunction base class methods
void init() override
overwrite IFunction base class method, which declare function parameters
void setMatrixWorkspace(std::shared_ptr< const API::MatrixWorkspace > workspace, size_t wi, double startX, double endX) override
Set matrix workspace.
void setFwhm(const double w) override
Sets the parameters such that FWHM = w.
void lowerConstraint0(const std::string &paramName)
constrain all parameters to be non-negative
std::vector< double > m_waveLength
container for storing wavelength values for each data point
double height() const override
Returns the height of the function.
void convertVoigtToPseudo(const double &voigtSigmaSq, const double &voigtGamma, double &H, double &eta) const
convert voigt params to pseudo voigt params
void functionDerivLocal(API::Jacobian *out, const double *xValues, const size_t nData) override
Derivative evaluation method. Default is to calculate numerically.
std::string name() const override
overwrite IFunction base class methods
void functionDeriv(const API::FunctionDomain &domain, API::Jacobian &jacobian) override
Derivatives of function with respect to active parameters.
double fwhm() const override
Returns the peak FWHM.
void calWavelengthAtEachDataPoint(const double *xValues, const size_t &nData) const
method for updating m_waveLength
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::complex< double > MANTID_CURVEFITTING_DLL exponentialIntegral(const std::complex< double > &z)
Compute exp(z)*E1(z) where z is complex and E1(z) is the Exponential Integral.
std::shared_ptr< const IComponent > IComponent_const_sptr
Typdef of a shared pointer to a const IComponent.
Definition IComponent.h:165
std::shared_ptr< const Instrument > Instrument_const_sptr
Shared pointer to an const instrument object.
std::shared_ptr< Unit > Unit_sptr
Shared pointer to the Unit base class.
Definition Unit_fwd.h:17