diff options
Diffstat (limited to 'lmfit/lineshapes.py')
-rw-r--r-- | lmfit/lineshapes.py | 89 |
1 files changed, 46 insertions, 43 deletions
diff --git a/lmfit/lineshapes.py b/lmfit/lineshapes.py index 064a18c..7ecdc42 100644 --- a/lmfit/lineshapes.py +++ b/lmfit/lineshapes.py @@ -1,25 +1,24 @@ -"""Basic model line shapes and distribution functions.""" +"""Basic model lineshapes and distribution functions.""" -import warnings - -from numpy import (arctan, copysign, cos, exp, isnan, log, pi, real, sin, sqrt, - where) +from numpy import (arctan, copysign, cos, exp, isclose, isnan, log, log1p, + maximum, minimum, pi, real, sin, sqrt, where) +from scipy.special import betaln as betalnfcn from scipy.special import erf, erfc from scipy.special import gamma as gamfcn +from scipy.special import loggamma as loggammafcn from scipy.special import wofz log2 = log(2) s2pi = sqrt(2*pi) -spi = sqrt(pi) s2 = sqrt(2.0) # tiny had been numpy.finfo(numpy.float64).eps ~=2.2e16. # here, we explicitly set it to 1.e-15 == numpy.finfo(numpy.float64).resolution tiny = 1.0e-15 functions = ('gaussian', 'gaussian2d', 'lorentzian', 'voigt', 'pvoigt', - 'moffat', 'pearson7', 'breit_wigner', 'damped_oscillator', + 'moffat', 'pearson4', 'pearson7', 'breit_wigner', 'damped_oscillator', 'dho', 'logistic', 'lognormal', 'students_t', 'expgaussian', - 'doniach', 'donaich', 'skewed_gaussian', 'skewed_voigt', + 'doniach', 'skewed_gaussian', 'skewed_voigt', 'thermal_distribution', 'step', 'rectangle', 'exponential', 'powerlaw', 'linear', 'parabolic', 'sine', 'expsine', 'split_lorentzian') @@ -148,6 +147,26 @@ def moffat(x, amplitude=1, center=0., sigma=1, beta=1.): return amplitude / (((x - center)/max(tiny, sigma))**2 + 1)**beta +def pearson4(x, amplitude=1.0, center=0.0, sigma=1.0, expon=1.0, skew=0.0): + """Return a Pearson4 lineshape. + + Using the Wikipedia definition: + + pearson4(x, amplitude, center, sigma, expon, skew) = + amplitude*|gamma(expon + I skew/2)/gamma(m)|**2/(w*beta(expon-0.5, 0.5)) * (1+arg**2)**(-expon) * exp(-skew * arctan(arg)) + + where ``arg = (x-center)/sigma``, `gamma` is the gamma function and `beta` is the beta function. + + For more information, see: https://en.wikipedia.org/wiki/Pearson_distribution#The_Pearson_type_IV_distribution + + """ + expon = max(tiny, expon) + sigma = max(tiny, sigma) + arg = (x - center) / sigma + logprefactor = 2 * (real(loggammafcn(expon + skew * 0.5j)) - loggammafcn(expon)) - betalnfcn(expon - 0.5, 0.5) + return (amplitude / sigma) * exp(logprefactor - expon * log1p(arg * arg) - skew * arctan(arg)) + + def pearson7(x, amplitude=1.0, center=0.0, sigma=1.0, expon=1.0): """Return a Pearson7 lineshape. @@ -188,7 +207,7 @@ def damped_oscillator(x, amplitude=1.0, center=1., sigma=0.1): return amplitude/sqrt((1.0 - (x/center)**2)**2 + (2*sigma*x/center)**2) -def dho(x, amplitude=1., center=0., sigma=1., gamma=1.0): +def dho(x, amplitude=1., center=1., sigma=1., gamma=1.0): """Return a Damped Harmonic Oscillator. Similar to the version from PAN: @@ -201,16 +220,19 @@ def dho(x, amplitude=1., center=0., sigma=1., gamma=1.0): ``lp(x, center, sigma) = 1.0 / ((x+center)**2 + sigma**2)`` """ + factor = amplitude * sigma / pi bose = (1.0 - exp(-x/max(tiny, gamma))) if isinstance(bose, (int, float)): - bose = max(tiny, bose) + bose = not_zero(bose) else: bose[where(isnan(bose))] = tiny - bose[where(bose <= tiny)] = tiny + bose[where(abs(bose) <= tiny)] = tiny lm = 1.0/((x-center)**2 + sigma**2) lp = 1.0/((x+center)**2 + sigma**2) - return amplitude*sigma/pi*(lm - lp)/bose + return factor * where(isclose(x, 0.0), + 4*gamma*center/(center**2+sigma**2)**2, + (lm - lp)/bose) def logistic(x, amplitude=1., center=0., sigma=1.): @@ -289,20 +311,6 @@ def doniach(x, amplitude=1.0, center=0, sigma=1.0, gamma=0.0): return scale*cos(pi*gamma/2 + gm1*arctan(arg))/(1 + arg**2)**(gm1/2) -def donaich(x, amplitude=1.0, center=0, sigma=1.0, gamma=0.0): - """Return a Doniach Sunjic asymmetric lineshape. - - Function added here for backwards-compatibility, will emit a - `FutureWarning` when used. - - """ - msg = ('Please correct the name of your lineshape function: donaich --> ' - 'doniach. The incorrect spelling will be removed in a later ' - 'release.') - warnings.warn(FutureWarning(msg)) - return doniach(x, amplitude, center, sigma, gamma) - - def skewed_gaussian(x, amplitude=1.0, center=0.0, sigma=1.0, gamma=0.0): """Return a Gaussian lineshape, skewed with error function. @@ -397,8 +405,8 @@ def thermal_distribution(x, amplitude=1.0, center=0.0, kt=1.0, form='bose'): elif form.startswith('fermi'): offset = 1 else: - msg = "Invalid value ('%s') for argument 'form'; should be one of %s."\ - % (form, "'maxwell', 'fermi', or 'bose'") + msg = (f"Invalid value ('{form}') for argument 'form'; should be one " + "of 'maxwell', 'fermi', or 'bose'.") raise ValueError(msg) return real(1/(amplitude*exp((x - center)/not_zero(kt)) + offset + tiny*1j)) @@ -410,7 +418,7 @@ def step(x, amplitude=1.0, center=0.0, sigma=1.0, form='linear'): Starts at 0.0, ends at `amplitude`, with half-max at `center`, and rising with `form`: - - `'linear'` (default) = amplitude * min(1, max(0, arg)) + - `'linear'` (default) = amplitude * min(1, max(0, arg + 0.5)) - `'atan'`, `'arctan'` = amplitude * (0.5 + atan(arg)/pi) - `'erf'` = amplitude * (1 + erf(arg))/2.0 - `'logistic'` = amplitude * [1 - 1/(1 + exp(arg))] @@ -423,15 +431,14 @@ def step(x, amplitude=1.0, center=0.0, sigma=1.0, form='linear'): if form == 'erf': out = 0.5*(1 + erf(out)) elif form == 'logistic': - out = (1. - 1./(1. + exp(out))) + out = 1. - 1./(1. + exp(out)) elif form in ('atan', 'arctan'): out = 0.5 + arctan(out)/pi elif form == 'linear': - out[where(out < 0)] = 0.0 - out[where(out > 1)] = 1.0 + out = minimum(1, maximum(0, out + 0.5)) else: - msg = "Invalid value ('%s') for argument 'form'; should be one of %s."\ - % (form, "'erf', 'logistic', 'atan', 'arctan', or 'linear'") + msg = (f"Invalid value ('{form}') for argument 'form'; should be one " + "of 'erf', 'logistic', 'atan', 'arctan', or 'linear'.") raise ValueError(msg) return amplitude*out @@ -462,18 +469,14 @@ def rectangle(x, amplitude=1.0, center1=0.0, sigma1=1.0, if form == 'erf': out = 0.5*(erf(arg1) + erf(arg2)) elif form == 'logistic': - out = (1. - 1./(1. + exp(arg1)) - 1./(1. + exp(arg2))) + out = 1. - 1./(1. + exp(arg1)) - 1./(1. + exp(arg2)) elif form in ('atan', 'arctan'): out = (arctan(arg1) + arctan(arg2))/pi elif form == 'linear': - arg1[where(arg1 < 0)] = 0.0 - arg1[where(arg1 > 1)] = 1.0 - arg2[where(arg2 > 0)] = 0.0 - arg2[where(arg2 < -1)] = -1.0 - out = arg1 + arg2 + out = 0.5*(minimum(1, maximum(-1, arg1)) + minimum(1, maximum(-1, arg2))) else: - msg = "Invalid value ('%s') for argument 'form'; should be one of %s."\ - % (form, "'erf', 'logistic', 'atan', 'arctan', or 'linear'") + msg = (f"Invalid value ('{form}') for argument 'form'; should be one " + "of 'erf', 'logistic', 'atan', 'arctan', or 'linear'.") raise ValueError(msg) return amplitude*out @@ -501,7 +504,7 @@ def powerlaw(x, amplitude=1, exponent=1.0): def linear(x, slope=1.0, intercept=0.0): """Return a linear function. - linear(x, slope, interceps) = slope * x + intercept + linear(x, slope, intercept) = slope * x + intercept """ return slope * x + intercept |