Coverage for /Users/Newville/Codes/xraylarch/larch/math/fitpeak.py: 17%
81 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1"""
2Basic Fitting Models for 1-D data, simplifying fits to many standard line shapes.
4 usage:
5 ------
6 param_group = fit_peak(x, y, model, dy=None,
7 background='linear', form='linear')
9 arguments:
10 ---------
11 x array of values at which to calculate model
12 y array of values for model to try to match
13 dy array of values for uncertainty in y data to be matched.
14 model name of model to use. One of (case insensitive)
15 'linear', 'quadratic', 'step', 'rectangle',
16 'exponential', 'gaussian', 'lorentzian', 'voigt'
18 background name of background model to use. One of (case insensitive)
19 None, 'constant', 'linear', or 'quadratic'
20 this is ignored when model is 'linear' or 'quadratic'
21 form name of form to use for 'step' and 'rectangle' models.
22 One of (case insensitive):
23 'linear', 'erf', or 'atan'
24 output:
25 -------
26 param_group Group with fit parameters, and
27"""
29import numpy as np
30from scipy.special import gamma, gammaln, beta, betaln, erf, erfc, wofz
31from lmfit import Parameter, Minimizer
32from lmfit.model import Model
34from lmfit.models import (update_param_vals, LinearModel, ConstantModel,
35 QuadraticModel, PolynomialModel, GaussianModel,
36 LorentzianModel, VoigtModel, PseudoVoigtModel,
37 MoffatModel, Pearson7Model, StudentsTModel,
38 BreitWignerModel, LognormalModel,
39 DampedOscillatorModel,
40 DampedHarmonicOscillatorModel,
41 ExponentialGaussianModel, SkewedGaussianModel,
42 DoniachModel, PowerLawModel, ExponentialModel,
43 StepModel, RectangleModel, ExpressionModel,
44 update_param_vals)
47from .. import Group
48from .utils import index_nearest, index_of, savitzky_golay
50VALID_BKGS = ('constant', 'linear', 'quadratic')
53MODELS = {'constant': ConstantModel,
54 'linear': LinearModel,
55 'quadratic': QuadraticModel,
56 'step': StepModel,
57 'rectangle': RectangleModel,
58 'exponential': ExponentialModel,
59 'gaussian': GaussianModel,
60 'lorentzian': LorentzianModel,
61 'voigt': VoigtModel,
62 'pseudovoigt': PseudoVoigtModel,
63 'pearson7': Pearson7Model,
64 'dho': DampedHarmonicOscillatorModel,
65 'expgaussian': ExponentialGaussianModel,
66 'skewedgaussian': SkewedGaussianModel,
67 'exponential': ExponentialModel,
68 }
70# a better guess for step and rectangle models
71def step_guess(self, data, x=None, **kwargs):
72 if x is None:
73 return
74 ymin, ymax = min(data), max(data)
75 xmin, xmax = min(x), max(x)
76 ntest = min(2, len(data)/5)
77 step_up = (data[:ntest].mean() > data[-ntest:].mean())
79 dydx = savitzky_golay(np.gradient(data)/np.gradient(x), 5, 2)
80 if step_up:
81 cen = x[np.where(dydx==dydx.max())][0]
82 else:
83 cen = x[np.where(dydx==dydx.min())][0]
85 pars = self.make_params(amplitude=(ymax-ymin), center=cen)
86 pars['%ssigma' % self.prefix].set(value=(xmax-xmin)/5.0, min=0.0)
87 return update_param_vals(pars, self.prefix, **kwargs)
89def rect_guess(self, data, x=None, **kwargs):
90 if x is None:
91 return
92 ymin, ymax = min(data), max(data)
93 xmin, xmax = min(x), max(x)
95 ntest = min(2, len(data)/5)
96 step_up = (data[:ntest].mean() > data[-ntest:].mean())
99 dydx = savitzky_golay(np.gradient(data)/np.gradient(x), 5, 2)
100 cen1 = x[np.where(dydx==dydx.max())][0]
101 cen2 = x[np.where(dydx==dydx.min())][0]
102 if step_up:
103 center1 = cen1 # + (xmax+xmin)/4.0)/2.
104 center2 = cen2 # + 3*(xmax+xmin)/4.0)/2.
105 else:
106 center1 = cen2 # + (xmax+xmin)/4.0)/2.0
107 center2 = cen1 # + 3*(xmax+xmin)/4.0)/2.0
109 pars = self.make_params(amplitude=(ymax-ymin),
110 center1=center1, center2=center2)
112 pars['%ssigma1' % self.prefix].set(value=(xmax-xmin)/5.0, min=0.0)
113 pars['%ssigma2' % self.prefix].set(value=(xmax-xmin)/5.0, min=0.0)
114 return update_param_vals(pars, self.prefix, **kwargs)
116StepModel.guess = step_guess
117RectangleModel.guess = rect_guess
119def fit_peak(x, y, model, dy=None, background=None, form=None, step=None,
120 negative=False, use_gamma=False):
121 """fit peak to one a selection of simple 1d models
123 out = fit_peak(x, y, model, dy=None,
124 background='linear', form='linear')
126 arguments:
127 ---------
128 x array of values at which to calculate model
129 y array of values for model to try to match
130 dy array of values for uncertainty in y data to be matched.
131 model name of model to use. One of (case insensitive)
132 'linear', 'quadratic', 'step', 'rectangle',
133 'gaussian', 'lorentzian', 'voigt', 'exponential'
134 background name of background model to use. One of (case insensitive)
135 None, 'constant', 'linear', or 'quadratic'
136 this is ignored when model is 'linear' or 'quadratic'
137 form name of form to use for 'step' and 'rectangle' models.
138 One of (case insensitive):
139 'linear', 'erf', or 'atan'
140 negative True/False for whether peak or steps are expected to go down.
141 use_gamma True/False for whether to use separate gamma parameter for
142 voigt model.
143 output:
144 -------
145 Group with fit parameters, and more...
146 """
147 if form is None and step is not None:
148 form = step
149 out = Group(name='fit_peak result', x=x*1.0, y=y*1.0, dy=1.0,
150 model=model, background=background, form=form)
152 weight = None
153 if dy is not None:
154 out.dy = 1.0*dy
155 weight = 1.0/max(1.e-16, abs(dy))
157 if model.lower() not in MODELS:
158 raise ValueError('Unknown fit model: %s ' % model)
160 kwargs = dict(negative=negative, background=background,
161 form=form, weight=weight)
163 fitclass = MODELS[model.lower()]
164 if fitclass == VoigtModel:
165 kwargs['use_gamma'] = use_gamma
167 mod = fitclass(**kwargs)
168 pars = mod.guess(out.y, out.x)
170 if background is not None:
171 bkg = MODELS[background.lower()](prefix='bkg_')
172 bpars = bkg.guess(out.y, x=out.x)
173 for p, par in bpars.items():
174 par.value = 0.
175 par.vary = True
176 pars += bpars
177 mod += bkg
179 out.init_params = pars
181 result = mod.fit(out.y, params=pars, x=out.x) # , dy=out.dy)
182 out.fit = mod.eval(result.params, x=out.x)
183 out.fit_init = mod.eval(pars, x=out.x)
185 out.fit_details = result
186 out.chi_square = result.chisqr
187 out.chi_reduced = result.redchi
189 for attr in ('aic', 'bic', 'covar', 'rfactor', 'params', 'nvarys',
190 'nfree', 'ndata', 'var_names', 'nfev', 'success',
191 'errorbars', 'message', 'lmdif_message', 'residual'):
192 setattr(out, attr, getattr(result, attr, None))
194 if background is not None:
195 comps = mod.eval_components(x=out.x)
196 out.bkg = comps['bkg_']
197 return out