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

1""" 

2Basic Fitting Models for 1-D data, simplifying fits to many standard line shapes. 

3 

4 usage: 

5 ------ 

6 param_group = fit_peak(x, y, model, dy=None, 

7 background='linear', form='linear') 

8 

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' 

17 

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""" 

28 

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 

33 

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) 

45 

46 

47from .. import Group 

48from .utils import index_nearest, index_of, savitzky_golay 

49 

50VALID_BKGS = ('constant', 'linear', 'quadratic') 

51 

52 

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 } 

69 

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()) 

78 

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] 

84 

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) 

88 

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) 

94 

95 ntest = min(2, len(data)/5) 

96 step_up = (data[:ntest].mean() > data[-ntest:].mean()) 

97 

98 

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 

108 

109 pars = self.make_params(amplitude=(ymax-ymin), 

110 center1=center1, center2=center2) 

111 

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) 

115 

116StepModel.guess = step_guess 

117RectangleModel.guess = rect_guess 

118 

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 

122 

123 out = fit_peak(x, y, model, dy=None, 

124 background='linear', form='linear') 

125 

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) 

151 

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)) 

156 

157 if model.lower() not in MODELS: 

158 raise ValueError('Unknown fit model: %s ' % model) 

159 

160 kwargs = dict(negative=negative, background=background, 

161 form=form, weight=weight) 

162 

163 fitclass = MODELS[model.lower()] 

164 if fitclass == VoigtModel: 

165 kwargs['use_gamma'] = use_gamma 

166 

167 mod = fitclass(**kwargs) 

168 pars = mod.guess(out.y, out.x) 

169 

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 

178 

179 out.init_params = pars 

180 

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) 

184 

185 out.fit_details = result 

186 out.chi_square = result.chisqr 

187 out.chi_reduced = result.redchi 

188 

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)) 

193 

194 if background is not None: 

195 comps = mod.eval_components(x=out.x) 

196 out.bkg = comps['bkg_'] 

197 return out