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