def test_guess_from_peak(): """Regression test for guess_from_peak function (see GH #627).""" x = np.linspace(-5, 5) amplitude = 0.8 center = 1.7 sigma = 0.3 y = lineshapes.lorentzian(x, amplitude=amplitude, center=center, sigma=sigma) model = models.LorentzianModel() guess_increasing_x = model.guess(y, x=x) guess_decreasing_x = model.guess(y[::-1], x=x[::-1]) assert guess_increasing_x == guess_decreasing_x for param, value in zip(['amplitude', 'center', 'sigma'], [amplitude, center, sigma]): assert np.abs((guess_increasing_x[param].value - value)/value) < 0.5
def fit_spectrum(x, y, num_resonances): """ Iteratively identifies canditate resonances, performs a least-squares regression, and removes the identified resonance from the data. This function is `dumb' in the sense that it abdicates responsibility for hypothesis testing. Having both find_resonance and find_resonances might be a bad naming convention. Args: x (numpy.ndarray): A one-dimensional real-valued numpy array, the frequency domain variable. y (numpy.ndarray): A one-dimensional real-valued numpy array, the signal variable. num_resonances (type): Description of parameter `num_resonances`. Returns: type: Description of returned object. """ # First pass, picking out resonances one-by-one. # Build model for whole spectrum along the way. resonance_fit_results = [] spectrum_model = models.ConstantModel() y_copy = y for i in range(num_resonances): resonance_fit_results.append(find_resonance(x, y_copy)) y_copy = remove_resonance_from_data(y_copy, resonance_fit_results[-1]) # Make a Lorentzian model for each resonance, with prefixes for proper name-mangling. lorentzian_model = models.LorentzianModel() lorentzian_model.prefix = '_' + str(i) + '_' spectrum_model += lorentzian_model # Sum up all the offsets from each resonance model net_offset = sum(resonance_fit_results[i].params['c'].value for i in range(num_resonances)) # Build a dict of all the Lorentzian parameters lorentzian_params = {} for i in range(num_resonances): lorentzian_params['_' + str(i) + '_amplitude'] = resonance_fit_results[i].params['amplitude'].value lorentzian_params['_' + str(i) + '_center'] = resonance_fit_results[i].params['center'].value lorentzian_params['_' + str(i) + '_sigma'] = resonance_fit_results[i].params['sigma'].value # And re-adjust the best fit. spectrum_fit_result = spectrum_model.fit(y, x=x, c=net_offset, **lorentzian_params) return spectrum_fit_result
def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise', name=None, num_resonances=1, **kwargs): kwargs.update({'prefix': prefix, 'nan_policy': nan_policy, 'independent_vars': independent_vars}) # This method might be a little bit kludgy. I'm guessing there's a more # pythonic way to achieve the same end. constant_model = models.ConstantModel(**kwargs) # Creates a sequence of LorentzianModel objects with prefixes `_i_`, for # i = 0, 1, ..., num_resonances - 1, and sums them. # TODO: is there a more elegant way to achieve this behavior? CompositeModel # doesn't accept null operands; but maybe there's a better way. resonance_model = Model(lambda x: 0) for i in range(num_resonances): lorentzian_model = models.LorentzianModel(**kwargs) lorentzian_model.prefix = '_' + str(i) + '_' resonance_model += lorentzian_model super(SpectrumModel, self).__init__(constant_model, resonance_model, operator.add, **kwargs)
def lorentz(self, oversample_multiplier=1, delta_rp=0, mz_overlay=1): ''' Legacy lorentz lineshape analysis function ''' if self.resolving_power: # full width half maximum distance self.fwhm = (self.mz_exp / (self.resolving_power + delta_rp) ) #self.resolving_power) # stardart deviation sigma = self.fwhm / 2 # half width baseline distance hw_base_distance = (8 * sigma) #mz_domain = linspace(self.mz_exp - hw_base_distance, # self.mz_exp + hw_base_distance, datapoint) mz_domain = self.get_mz_domain(oversample_multiplier, mz_overlay) # gaussian_pdf = lambda x0, x, s: (1/ math.sqrt(2*math.pi*math.pow(s,2))) * math.exp(-1 * math.pow(x-x0,2) / 2*math.pow(s,2) ) model = models.LorentzianModel() amplitude = sigma * pi * self.abundance params = model.make_params(center=self.mz_exp, amplitude=amplitude, sigma=sigma) calc_abundance = model.eval(params=params, x=mz_domain) return mz_domain, calc_abundance else: raise LookupError( 'resolving power is not defined, try to use set_max_resolving_power()' )
def test_height_and_fwhm_expression_evalution_in_builtin_models(): """Assert models do not throw an ZeroDivisionError.""" mod = models.GaussianModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9) params.update_constraints() mod = models.LorentzianModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9) params.update_constraints() mod = models.SplitLorentzianModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, sigma_r=1.0) params.update_constraints() mod = models.VoigtModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=1.0) params.update_constraints() mod = models.PseudoVoigtModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, fraction=0.5) params.update_constraints() mod = models.MoffatModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, beta=0.0) params.update_constraints() mod = models.Pearson7Model() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, expon=1.0) params.update_constraints() mod = models.StudentsTModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9) params.update_constraints() mod = models.BreitWignerModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, q=0.0) params.update_constraints() mod = models.LognormalModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9) params.update_constraints() mod = models.DampedOscillatorModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9) params.update_constraints() mod = models.DampedHarmonicOscillatorModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=0.0) params.update_constraints() mod = models.ExponentialGaussianModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=0.0) params.update_constraints() mod = models.SkewedGaussianModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=0.0) params.update_constraints() mod = models.SkewedVoigtModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=0.0, skew=0.0) params.update_constraints() mod = models.DoniachModel() params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, gamma=0.0) params.update_constraints() mod = models.StepModel() for f in ('linear', 'arctan', 'erf', 'logistic'): params = mod.make_params(amplitude=1.0, center=0.0, sigma=0.9, form=f) params.update_constraints() mod = models.RectangleModel() for f in ('linear', 'arctan', 'erf', 'logistic'): params = mod.make_params(amplitude=1.0, center1=0.0, sigma1=0.0, center2=0.0, sigma2=0.0, form=f) params.update_constraints()
def test_guess_modelparams(): """Tests for the 'guess' function of built-in models.""" x = np.linspace(-10, 10, 501) mod = models.ConstantModel() y = 6.0 + x*0.005 pars = mod.guess(y) assert_allclose(pars['c'].value, 6.0, rtol=0.01) mod = models.ComplexConstantModel(prefix='f_') y = 6.0 + x*0.005 + (4.0 - 0.02*x)*1j pars = mod.guess(y) assert_allclose(pars['f_re'].value, 6.0, rtol=0.01) assert_allclose(pars['f_im'].value, 4.0, rtol=0.01) mod = models.QuadraticModel(prefix='g_') y = -0.2 + 3.0*x + 0.005*x**2 pars = mod.guess(y, x=x) assert_allclose(pars['g_a'].value, 0.005, rtol=0.01) assert_allclose(pars['g_b'].value, 3.0, rtol=0.01) assert_allclose(pars['g_c'].value, -0.2, rtol=0.01) mod = models.PolynomialModel(4, prefix='g_') y = -0.2 + 3.0*x + 0.005*x**2 - 3.3e-6*x**3 + 1.e-9*x**4 pars = mod.guess(y, x=x) assert_allclose(pars['g_c0'].value, -0.2, rtol=0.01) assert_allclose(pars['g_c1'].value, 3.0, rtol=0.01) assert_allclose(pars['g_c2'].value, 0.005, rtol=0.1) assert_allclose(pars['g_c3'].value, -3.3e-6, rtol=0.1) assert_allclose(pars['g_c4'].value, 1.e-9, rtol=0.1) mod = models.GaussianModel(prefix='g_') y = lineshapes.gaussian(x, amplitude=2.2, center=0.25, sigma=1.3) y += np.random.normal(size=len(x), scale=0.004) pars = mod.guess(y, x=x) assert_allclose(pars['g_amplitude'].value, 3, rtol=2) assert_allclose(pars['g_center'].value, 0.25, rtol=1) assert_allclose(pars['g_sigma'].value, 1.3, rtol=1) mod = models.LorentzianModel(prefix='l_') pars = mod.guess(y, x=x) assert_allclose(pars['l_amplitude'].value, 3, rtol=2) assert_allclose(pars['l_center'].value, 0.25, rtol=1) assert_allclose(pars['l_sigma'].value, 1.3, rtol=1) mod = models.SplitLorentzianModel(prefix='s_') pars = mod.guess(y, x=x) assert_allclose(pars['s_amplitude'].value, 3, rtol=2) assert_allclose(pars['s_center'].value, 0.25, rtol=1) assert_allclose(pars['s_sigma'].value, 1.3, rtol=1) assert_allclose(pars['s_sigma_r'].value, 1.3, rtol=1) mod = models.VoigtModel(prefix='l_') pars = mod.guess(y, x=x) assert_allclose(pars['l_amplitude'].value, 3, rtol=2) assert_allclose(pars['l_center'].value, 0.25, rtol=1) assert_allclose(pars['l_sigma'].value, 1.3, rtol=1) mod = models.SkewedVoigtModel(prefix='l_') pars = mod.guess(y, x=x) assert_allclose(pars['l_amplitude'].value, 3, rtol=2) assert_allclose(pars['l_center'].value, 0.25, rtol=1) assert_allclose(pars['l_sigma'].value, 1.3, rtol=1)
def fitData(x, y): peaks, _ = signal.find_peaks(y, height=0.01, width=5) print peaks model_1 = models.GaussianModel(prefix='m1_') model_2 = models.GaussianModel(prefix='m2_') model_3 = models.GaussianModel(prefix='m3_') model_4 = models.LinearModel(prefix='l3_') model_5 = models.LorentzianModel(prefix='m4_') model = model_1 + model_2 + model_3 + model_4 + model_5 model_1.set_param_hint("amplitude", min=0.002, max=0.1) model_1.set_param_hint("sigma", min=0.00, max=0.025) model_1.set_param_hint("center", min=x[peaks[1]] - 0.05, max=x[peaks[1]] + 0.05) params_1 = model_1.make_params(amplitude=0.05, center=x[peaks[1]], sigma=0.01) model_2.set_param_hint("amplitude", min=1e-5, max=1e-3) model_2.set_param_hint("sigma", min=0.0005, max=0.08) model_2.set_param_hint("center", min=x[peaks[2]] - 0.075, max=x[peaks[2]] + 0.075) params_2 = model_2.make_params(amplitude=0.005, center=x[peaks[2]], sigma=0.03) model_3.set_param_hint("amplitude", min=1e-6, max=1e-2) model_3.set_param_hint("sigma", min=0.005, max=0.08) model_3.set_param_hint("center", min=x[peaks[1]] - 0.05, max=x[peaks[1]] + 0.1) params_3 = model_3.make_params(amplitude=1e-3, center=x[peaks[1]] + 0.040, sigma=0.04) """ model_4.set_param_hint("intercept", min = 1e-15, max = np.min(y)*1.5) model_4.set_param_hint("slope", min = 1e-16)""" params_4 = model_4.make_params(slope=1e-9, intercept=np.min(y)) model_5.set_param_hint("amplitude", min=1e-6, max=0.06) model_5.set_param_hint("sigma", min=0.00, max=0.025) model_5.set_param_hint("center", min=x[peaks[0]] - 0.05, max=x[peaks[0]] + 0.05) params_5 = model_5.make_params(amplitude=0.05, center=x[peaks[0]], sigma=0.01) params_1.update(params_2) params_1.update(params_3) params_1.update(params_4) params_1.update(params_5) params = params_1 output = model.fit(y, params, x=x) print output.fit_report() output.plot(data_kws={'markersize': 1}) plt.plot(x, y) plt.semilogy() plt.show(block=False) return output
def fit_peak(self, mz_extend=6, delta_rp=0, model='Gaussian'): ''' Model and fit peak lineshape by defined function - using lmfit module Do not oversample/resample/interpolate data points Better to go back to time domain and perform more zero filling Models allowed: Gaussian, Lorentz, Voigt Returns the calculated mz domain, initial defined abundance profile, and the fit peak results object from lmfit module mz_extend here extends the x-axis domain so that we have sufficient points either side of the apex to fit. Takes about 10ms per peak ''' start_index = self.start_scan - mz_extend if not self.start_scan == 0 else 0 final_index = self.final_scan + mz_extend if not self.final_scan == len( self._ms_parent.mz_exp_profile) else self.final_scan # check if MSPeak contains the resolving power info if self.resolving_power: # full width half maximum distance self.fwhm = (self.mz_exp / (self.resolving_power + delta_rp)) mz_domain = self._ms_parent.mz_exp_profile[start_index:final_index] abundance_domain = self._ms_parent.abundance_profile[ start_index:final_index] if model == 'Gaussian': # stardard deviation sigma = self.fwhm / (2 * sqrt(2 * log(2))) amplitude = (sqrt(2 * pi) * sigma) * self.abundance model = models.GaussianModel() params = model.make_params(center=self.mz_exp, amplitude=amplitude, sigma=sigma) elif model == 'Lorentz': # stardard deviation sigma = self.fwhm / 2 amplitude = sigma * pi * self.abundance model = models.LorentzianModel() params = model.make_params(center=self.mz_exp, amplitude=amplitude, sigma=sigma) elif model == 'Voigt': # stardard deviation sigma = self.fwhm / 3.6013 amplitude = (sqrt(2 * pi) * sigma) * self.abundance model = models.VoigtModel() params = model.make_params(center=self.mz_exp, amplitude=amplitude, sigma=sigma, gamma=sigma) else: raise LookupError('model lineshape not known or defined') #calc_abundance = model.eval(params=params, x=mz_domain) #Same as initial fit, returned in fit_peak object fit_peak = model.fit(abundance_domain, params=params, x=mz_domain) return mz_domain, fit_peak else: raise LookupError( 'resolving power is not defined, try to use set_max_resolving_power()' )
# in this test version are nearly as naive as possible. Since this is a module that is # worth getting Right with a capital 'R', and since a lot of people have reinvented # this wheel before, it would behoove me to review prior art. In particular, it's likely # that there are standard algorithms and hypothesis tests that are well-known in # the NMR, HEP, and astronomy communities. Check out any CERN docs on spectrum-fitting; # they're probably the gold standard. In short, this is abeing written to be deprecated. # TODO: Review and fix redundancy between SpectrumModel class and spectrum model # building in fit_spectrum. DRY it out! # TODO: SpectrumModel constructor is ugly. # TODO: spectrum_fit is really quite slow. # TODO: this actually can be rewritten more intelligently. # Make the guessing functions the guess method of the SpectrumModel class. # Global definitions RESONANCE_MODEL = models.ConstantModel() + models.LorentzianModel() # Classes class SpectrumModel(CompositeModel): """ An lmfit Model representing a spectrum of finitely-many Lorentzian resonances with parameters ``c'', ``_i_amplitude``, ``_i_center``, ``_i_sigma`` for i = 0, 1, ..., num_resonances - 1 Args: num_resonances (int): The number of Lorentzian resonances. """