def set_params(peaks): """ This module takes in the list of peaks from the peak detection modules, and then uses that to initialize parameters for a set of Pseudo-Voigt models that are not yet fit. There is a single model for every peak. Args: peaks (list): A list containing the x and y-values (in tuples) of the peaks. Returns: mod (lmfit.models.PseudoVoigtModel or lmfit.model.CompositeModel): This is an array of the initialized Pseudo-Voigt models. The array contains all of the values that are found in `pars` that are fed to an lmfit lorentzian model class. pars (lmfit.parameter.Parameters): An array containing the parameters for each peak that were generated through the use of a Lorentzian fit. The pars array contains a center value, a height, a sigma, and an amplitude value. The center value is allowed to vary +- 10 wavenumber from the peak max that was detected in scipy. Some wiggle room was allowed to help mitigate problems from slight issues in the peakdetect algorithm for peaks that might have relatively flat maxima. The height value was allowed to vary between 0 and 1, as it is assumed the y-values are normalized. Sigma is set to a maximum of 500, as we found that giving it an unbound maximum led to a number of peaks that were unrealistic for Raman spectra (ie, they were far too broad, and shallow, to correspond to real data. Finally, the amplitude for the peak was set to a minimum of 0, to prevent negatives. """ # handling errors in inputs if not isinstance(peaks, list): raise TypeError('Passed value of `peaks` is not a list! Instead, it is: ' + str(type(peaks))) for i, _ in enumerate(peaks): if not isinstance(peaks[i], tuple): raise TypeError("""Passed value of `peaks[{}]` is not a tuple. Instead, it is: """.format(i) + str(type(peaks[i]))) peak_list = [] for i, _ in enumerate(peaks): prefix = 'p{}_'.format(i+1) peak = PseudoVoigtModel(prefix=prefix) if i == 0: pars = peak.make_params() else: pars.update(peak.make_params()) pars[prefix+'center'].set(peaks[i][0], vary=False) pars[prefix+'height'].set(peaks[i][1], vary=False) pars[prefix+'sigma'].set(50, min=0, max=500) pars[prefix+'amplitude'].set(min=0) peak_list.append(peak) if i == 0: mod = peak_list[i] else: mod = mod + peak_list[i] return mod, pars
def add_peak(self, peak): """Adds a new Peak to the Model list.""" if peak.region is not self._region: logger.error("peak with ID {} does not belong to region ID {}" "".format(peak.ID, self._region.ID)) raise ValueError("Peak does not belong to Region") if peak.model_name == "PseudoVoigt": model = PseudoVoigtModel(prefix=peak.prefix) # model.set_param_hint("fraction", vary=False, value=0.2) params = model.make_params() self._params += params fwname = "{}fwhm".format(peak.prefix) sigmaname = "{}sigma".format(peak.prefix) ampname = "{}amplitude".format(peak.prefix) centername = "{}center".format(peak.prefix) # alphaname = "{}fraction".format(peak.prefix) params[fwname].set(value=params[fwname].value, vary=True, min=0) params[sigmaname].set(expr="{}/2".format(fwname)) params[ampname].set(min=0) params[centername].set(min=0) elif peak.model_name == "Doniach": model = MyDoniach(prefix=peak.prefix) params = model.make_params() self._params += params sigmaname = "{}sigma".format(peak.prefix) ampname = "{}amplitude".format(peak.prefix) centername = "{}center".format(peak.prefix) gammaname = "{}gamma".format(peak.prefix) params[sigmaname].set(min=0) params[ampname].set(min=0) params[centername].set(min=0) params[gammaname].set(vary=True) elif peak.model_name == "ConvDoniach": model = ConvolvedDoniach(prefix=peak.prefix) params = model.make_params() self._params += params sigmaname = "{}sigma".format(peak.prefix) ampname = "{}amplitude".format(peak.prefix) centername = "{}center".format(peak.prefix) gammaname = "{}gamma".format(peak.prefix) convname = "{}conv".format(peak.prefix) params[sigmaname].set(min=0) params[ampname].set(min=0) params[centername].set(min=0) params[gammaname].set(vary=True) params[convname].set(min=0) else: raise NotImplementedError("Only PseudoVoigt models supported") self._single_models[peak.prefix] = model
def test_model_nan_policy(self): x = np.linspace(0, 10, 201) np.random.seed(0) y = gaussian(x, 10.0, 6.15, 0.8) y += gaussian(x, 8.0, 6.35, 1.1) y += gaussian(x, 0.25, 6.00, 7.5) y += np.random.normal(size=len(x), scale=0.5) y[55] = y[91] = np.nan mod = PseudoVoigtModel() params = mod.make_params(amplitude=20, center=5.5, sigma=1, fraction=0.25) params['fraction'].vary = False # with raise, should get a ValueError result = lambda: mod.fit(y, params, x=x, nan_policy='raise') self.assertRaises(ValueError, result) # with propagate, should get no error, but bad results result = mod.fit(y, params, x=x, nan_policy='propagate') self.assertTrue(result.success) self.assertTrue(np.isnan(result.chisqr)) self.assertTrue(np.isnan(result.aic)) self.assertFalse(result.errorbars) self.assertTrue(result.params['amplitude'].stderr is None) self.assertTrue(abs(result.params['amplitude'].value - 20.0) < 0.001) # with omit, should get good results result = mod.fit(y, params, x=x, nan_policy='omit') self.assertTrue(result.success) self.assertTrue(result.chisqr > 2.0) self.assertTrue(result.aic < -100) self.assertTrue(result.errorbars) self.assertTrue(result.params['amplitude'].stderr > 0.1) self.assertTrue(abs(result.params['amplitude'].value - 20.0) < 5.0) self.assertTrue(abs(result.params['center'].value - 6.0) < 0.5)
def make_model(peak_positions, fwhm=0.05, max_fwhm=0.5, pos_range=0.5, amplitude=1000.): n_peaks = len(peak_positions) pars = Parameters() bg = LinearModel(prefix='bg_') pars.update(bg.make_params(slope=0, intercept=0)) mod = bg #pars['bg_intercept'].set(vary=True) #pars['bg_slope'].set(vary=True) for i in range(n_peaks): prefix = 'pk{}_'.format(i) peak = PseudoVoigtModel(prefix=prefix) # Set this zero pars.update(peak.make_params()) pars[prefix + 'center'].set(peak_positions[i], min=peak_positions[i] - pos_range, max=peak_positions[i] + pos_range, vary=True) pars[prefix + 'sigma'].set(fwhm, min=0., max=max_fwhm, vary=True) pars[prefix + 'amplitude'].set(amplitude, min=0., vary=True) pars[prefix + 'fraction'].set(0.0, min=0., max=1., vary=True) mod += peak return mod, pars
def set_params(peaks): """ This function takes in the list of peaks from the peak detection modules, and then uses that to initialize parameters for a set of Pseudo-Voigt models that are not yet fit. There is a single model for every peak. Args: peaks (list): A list containing tuples of the x_data (wavenumber) and y_data (counts) values of the peaks. Returns: mod (lmfit.models.PseudoVoigtModel or lmfit.model.CompositeModel): This is an array of the initialized pseudo-Voigt models. The array contains all of the values that are found in `pars` that are fed to an lmfit lorentzian model class. pars (lmfit.parameter.Parameters): An array containing the parameters for each peak that were generated through the use of a Lorentzian fit. The pars array contains values for fraction, center, height, sigma, the full width at half maximum (fwhm = 2*sigma), and amplitude. """ # handling errors in inputs if not isinstance(peaks, list): raise TypeError( 'Passed value of `peaks` is not a list! Instead, it is: ' + str(type(peaks))) for i, peak in enumerate(peaks): if not isinstance(peak, tuple): raise TypeError("""The {} value of `peaks` is not a tuple. Instead, it is: """.format(i) + str(type(peak))) peak_list = [] for i, value in enumerate(peaks): prefix = 'p{}_'.format(i + 1) peak = PseudoVoigtModel(prefix=prefix) if i == 0: pars = peak.make_params() else: pars.update(peak.make_params()) # constraints on profile desciptors pars[prefix + 'center'].set(value[0], vary=False) pars[prefix + 'height'].set(min=0.1 * value[1]) pars[prefix + 'sigma'].set(10, min=1, max=100) pars[prefix + 'amplitude'].set(100 * value[1], min=0) peak_list.append(peak) if i == 0: mod = peak_list[i] else: mod = mod + peak_list[i] return mod, pars
def test_model_nan_policy(self): """Tests for nan_policy with NaN values in the input data.""" x = np.linspace(0, 10, 201) np.random.seed(0) y = gaussian(x, 10.0, 6.15, 0.8) y += gaussian(x, 8.0, 6.35, 1.1) y += gaussian(x, 0.25, 6.00, 7.5) y += np.random.normal(size=len(x), scale=0.5) # with NaN values in the input data y[55] = y[91] = np.nan mod = PseudoVoigtModel() params = mod.make_params(amplitude=20, center=5.5, sigma=1, fraction=0.25) params['fraction'].vary = False # with raise, should get a ValueError result = lambda: mod.fit(y, params, x=x, nan_policy='raise') msg = ( 'NaN values detected in your input data or the output of your ' 'objective/model function - fitting algorithms cannot handle this!' ) self.assertRaisesRegex(ValueError, msg, result) # with propagate, should get no error, but bad results result = mod.fit(y, params, x=x, nan_policy='propagate') self.assertTrue(result.success) self.assertTrue(np.isnan(result.chisqr)) self.assertTrue(np.isnan(result.aic)) self.assertFalse(result.errorbars) self.assertTrue(result.params['amplitude'].stderr is None) self.assertTrue(abs(result.params['amplitude'].value - 20.0) < 0.001) # with omit, should get good results result = mod.fit(y, params, x=x, nan_policy='omit') self.assertTrue(result.success) self.assertTrue(result.chisqr > 2.0) self.assertTrue(result.aic < -100) self.assertTrue(result.errorbars) self.assertTrue(result.params['amplitude'].stderr > 0.1) self.assertTrue(abs(result.params['amplitude'].value - 20.0) < 5.0) self.assertTrue(abs(result.params['center'].value - 6.0) < 0.5) # with 'wrong_argument', should get a ValueError err_msg = r"nan_policy must be 'propagate', 'omit', or 'raise'." with pytest.raises(ValueError, match=err_msg): mod.fit(y, params, x=x, nan_policy='wrong_argument')
def prepare_for_fitting(self, poly_order, maxwidth, centerrange): """ :param x_center: numpy array of initial x values at picked centers :param y_center: numpy array of initial y values at picked centers :param fwhm: single float number for initial fwhm value """ self.set_baseline(poly_order) baseline_mod = PolynomialModel(poly_order, prefix='b_') mod = baseline_mod pars = baseline_mod.make_params() peakinfo = {} for i in range(poly_order + 1): prefix = "b_c{0:d}".format(i) pars[prefix].set(value=self.baseline_in_queue[i]['value'], vary=self.baseline_in_queue[i]['vary']) i = 0 for peak in self.peaks_in_queue: prefix = "p{0:d}_".format(i) peak_mod = PseudoVoigtModel(prefix=prefix, ) pars.update(peak_mod.make_params()) pars[prefix + 'center'].set(value=peak['center'], min=peak['center'] - centerrange, max=peak['center'] + centerrange, vary=peak['center_vary']) pars[prefix + 'sigma'].set(value=peak['sigma'], min=0.0, vary=peak['sigma_vary'], max=maxwidth) pars[prefix + 'amplitude'].set(value=peak['amplitude'], min=0, vary=peak['amplitude_vary']) pars[prefix + 'fraction'].set(value=peak['fraction'], min=0., max=1., vary=peak['fraction_vary']) peakinfo[prefix + 'phasename'] = peak['phasename'] peakinfo[prefix + 'h'] = peak['h'] peakinfo[prefix + 'k'] = peak['k'] peakinfo[prefix + 'l'] = peak['l'] mod += peak_mod i += 1 self.parameters = pars self.peakinfo = peakinfo self.fit_model = mod
def test_param_hint_explicit_value(self): # tests Github Issue 384 pmod = PseudoVoigtModel() params = pmod.make_params(sigma=2, fraction=0.77) assert_allclose(params['fraction'].value, 0.77, rtol=0.01)
def fitMo3d(filename, groupNo, regionNo, E_b_min, E_b_max, doplot=False): """Fit a Moly 3d signal using elemental and oxide components (two for each peak, 3/2 and 5/2). A shirley background is subtracted first. The elemental Mo components are fittet to the PseudoVoigt line shape. Oxide components are fitted to a Gaussian line shape. Parameters ---------- filename : string location of the data file, expects an SpecsLab xml file. groupNo : int index of the group of spectra (from 0) regionNo : int index of the spectrum that should be fitted (from 0) E_b_min : float region boundary E_b_max : float region boundary doplot : bool wether or not to plot the fit and components (default False) """ #load data from SpecsLab xml file and select region region = get_region(filename, groupNo, regionNo) # determine index of left and right boundary index1 = np.where(abs(region.x_be - (E_b_min)) < 1e-10) index2 = np.where(abs(region.x_be - (E_b_max)) < 1e-10) E = region.x_be[index1[0][0]:index2[0][0]] #binding energy scale s = region.y_avg_counts_mcd[index1[0][0]:index2[0][0]] #signal #signal with background substacted epsilon, sb, B = remove_shirley_background(s, E, 3, 1e-6) ###################### # set up model components and set constraints for the fit ###################### pre11 = 'Mo3d_32_1_' mod11 = PseudoVoigtModel(prefix=pre11) pars = mod11.make_params() pars.add('delta', value=3, min=2, max=4) pars[pre11 + 'amplitude'].set(9000, min=500, max=100000) pars[pre11 + 'center'].set(-231.2, min=-231.4, max=-231) pars[pre11 + 'sigma'].set(0.3, min=0.1, max=0.4) pars[pre11 + 'fraction'].set(0.5, min=0.1, max=1) mod21 = PseudoVoigtModel(prefix='Mo3d_52_1_') pars.update(mod21.make_params()) pars['Mo3d_52_1_amplitude'].set( expr='3/2*Mo3d_32_1_amplitude') #(12000, min=500, max=100000) pars['Mo3d_52_1_center'].set(expr='Mo3d_32_1_center+delta' ) #same distance between 3/2 and 5/2 peaks #pars['Mo3d_52_1_sigma' ].set(expr='1.0*Mo3d_32_1_sigma') #all have same sigma value pars['Mo3d_52_1_fraction'].set( expr='1.0*Mo3d_32_1_fraction') #(0.5, min=0.2, max=1) pre12 = 'MoO23d_32_2_' mod12 = GaussianModel(prefix=pre12) pars.update(mod12.make_params()) pars[pre12 + 'amplitude'].set(2050, min=500, max=100000) pars[pre12 + 'center'].set(-234.5, min=-236, max=-234) pars[pre12 + 'sigma'].set(0.3, min=0.2, max=2) pre22 = 'MoO23d_52_2_' mod22 = GaussianModel(prefix=pre22) pars.update(mod22.make_params()) pars[pre22 + 'amplitude'].set( expr='3/2*MoO23d_32_2_amplitude') #(1050, min=500, max=100000) pars[pre22 + 'center'].set(expr='MoO23d_32_2_center+delta' ) #same distance between 3/2 and 5/2 peaks pars[pre22 + 'sigma'].set( expr='1.0*MoO23d_32_2_sigma') #all have same sigma value pre13 = 'MoO23d_32_3_' mod13 = GaussianModel(prefix=pre13) pars.update(mod13.make_params()) pars[pre13 + 'amplitude'].set(2050, min=500, max=100000) pars[pre13 + 'center'].set(-235.5, min=-237, max=-234) pars[pre13 + 'sigma'].set(0.3, min=0.2, max=2) pre23 = 'MoO23d_52_3_' mod23 = GaussianModel(prefix=pre23) pars.update(mod23.make_params()) pars[pre23 + 'amplitude'].set( expr='3/2*MoO23d_32_3_amplitude') #(1050, min=500, max=100000) pars[pre23 + 'center'].set(expr='MoO23d_32_3_center+delta' ) #same distance between 3/2 and 5/2 peaks pars[pre23 + 'sigma'].set( expr='1.0*MoO23d_32_3_sigma') #all have same sigma value #composite model is a sum of the components mod = mod11 + mod21 + mod12 + mod22 + mod13 + mod23 #perform the fit out = mod.fit(sb, pars, x=E) print(out.fit_report(min_correl=0.25)) if (doplot): fig = plt.figure(None, figsize=(6, 4)) #plot individual components comps = mod.eval_components(params=out.params, x=E) labels = [ 'Mo', 'Mo', 'MoO$_3$', 'MoO$_3$', 'MoO$_2$', 'MoO$_2$', 'MoO$_2$', 'MoO$_2$' ] for n, key in enumerate(comps): if ((n % 2) == 0): plt.plot(-E, comps[key] / 10**3, color=colors[n], label=labels[n]) else: plt.plot(-E, comps[key] / 10**3, color=colors[n - 1]) #plt.fill_between(-E, 0.0, comps[key]/10**3, facecolor=colors[int(i/2)], alpha=0.5) #plot measured signal (dots) and best fit (sum of components) plt.plot(-E, sb / 10**3, 'k.', label='measurement') #plt.plot( -E , s/10**3 , 'k-') #plt.plot( -E , B/10**3 , 'k--') plt.plot(-E, out.best_fit / 10**3, color=colors[1], label='total fit') ax = mpl.pyplot.gca() ax.set_xlim(241, -E_b_max) plt.xlabel('binding energy (eV)') plt.ylabel('counts (arb. u.)') print(out.best_values['MoO23d_32_2_center'] - out.best_values['Mo3d_32_1_center']) print(out.best_values['MoO23d_32_3_center'] - out.best_values['Mo3d_32_1_center']) ## reference : also plot pure Mo signal #load data from SpecsLab xml file and select region region = get_region(filename, 3, 2) # determine index of left and right boundary index1 = np.where(abs(region.x_be - (-235)) < 1e-10) index2 = np.where(abs(region.x_be - (E_b_max)) < 1e-10) E = region.x_be[index1[0][0]:index2[0][0]] #binding energy scale s = region.y_avg_counts_mcd[index1[0][0]:index2[0][0]] #signal epsilon, sb, B = remove_shirley_background(s, E, 3, 1e-6) plt.plot(-E, sb / (6 * 10**3), 'k--', label='pure Mo') plt.legend() fig.tight_layout()
def fitpureMo3d(filename, groupNo, regionNo, E_b_min, E_b_max, doplot=False): """Fit a Moly 3d signal using elemental and oxide components (two for each peak, 3/2 and 5/2). A shirley background is subtracted first. The elemental Mo components are fittet to the PseudoVoigt line shape. Oxide components are fitted to a Gaussian line shape. Parameters ---------- filename : string location of the data file, expects an SpecsLab xml file. groupNo : int index of the group of spectra (from 0) regionNo : int index of the spectrum that should be fitted (from 0) E_b_min : float region boundary E_b_max : float region boundary doplot : bool wether or not to plot the fit and components (default False) """ #load data from SpecsLab xml file and select region region = get_region(filename, groupNo, regionNo) #Sb = VAMAS.VAMASExperiment('P006_Sb_surveyf.vms'); #regions = [data[0][2], data[1][1], data[2][1]] print(region.x_be) # determine index of left and right boundary index1 = np.where(abs(region.x_be - (E_b_min)) < 1e-10) index2 = np.where(abs(region.x_be - (E_b_max)) < 1e-10) E = region.x_be[index1[0][0]:index2[0][0]] #binding energy scale s = region.y_avg_counts_mcd[index1[0][0]:index2[0][0]] #signal #signal with background substacted epsilon, sb, B = remove_shirley_background(s, E, 3, 1e-6) ###################### # set up model components and set constraints for the fit ###################### pre11 = 'Mo3d_32_1_' mod11 = PseudoVoigtModel(prefix=pre11) pars = mod11.make_params() pars.add('delta', value=3, min=2, max=5) pars[pre11 + 'amplitude'].set(9000, min=500, max=100000) pars[pre11 + 'center'].set(-231, min=-231, max=-230) pars[pre11 + 'sigma'].set(0.3, min=0.2, max=1) pars[pre11 + 'fraction'].set(0.5, min=0.2, max=1) mod21 = PseudoVoigtModel(prefix='Mo3d_52_1_') pars.update(mod21.make_params()) pars['Mo3d_52_1_amplitude'].set( expr='3/2*Mo3d_32_1_amplitude') #(12000, min=500, max=100000) pars['Mo3d_52_1_center'].set(expr='Mo3d_32_1_center+delta' ) #same distance between 3/2 and 5/2 peaks #pars['Mo3d_52_1_sigma' ].set(expr='1.0*Mo3d_32_1_sigma') #all have same sigma value pars['Mo3d_52_1_fraction'].set( expr='1.0*Mo3d_32_1_fraction') #(0.5, min=0.2, max=1) #composite model is a sum of the components mod = mod11 + mod21 #perform the fit out = mod.fit(sb, pars, x=E) if (doplot): plt.figure() #plot individual components comps = mod.eval_components(params=out.params, x=E) i = 0 for key in comps: plt.plot(E, comps[key] / 10**3, color=colors[i], label=key) plt.fill_between(E, 0.0, comps[key] / 10**3, facecolor=colors[i], alpha=0.5) i = i + 1 #plot measured signal (dots) and best fit (sum of components) plt.plot(E, sb / 10**3, 'k.') plt.plot(E, s / 10**3, 'k-') plt.plot(E, B / 10**3, 'k--') plt.plot(E, out.best_fit / 10**3, color=colors[1]) plt.legend() ax = mpl.pyplot.gca() ax.set_xlim(E_b_min, E_b_max) plt.xlabel('binding energy (eV)') plt.ylabel('cps ($10^3$)') print(out.fit_report(min_correl=0.25))
def fit_sb_3d(inputs): """Fit an Antimony 3d signal using four Sb components (two for each peak, 3/2 and 5/2). A shirley background is subtracted first. The Sb components are fittet to the PseudoVoigt line shape. An Oxygen component is fitted to a Gaussian line shape. Parameters ---------- region : object region class object of the spectrum that should be fitted (from 0) e_b_min : float region boundary e_b_max : float region boundary doplot : bool wether or not to plot the fit and components (default False) """ (region, e_b_min, e_b_max, name, doplot, title) = inputs print((region, e_b_min, e_b_max)) # determine index of left and right boundary index1 = np.where(abs(region.x_be - (e_b_min)) < 1e-10) index2 = np.where(abs(region.x_be - (e_b_max)) < 1e-10) # select energy scale and signal within boundaries energy = region.x_be[index1[0][0]:index2[0][0]] signal = region.y_avg_counts_mcd[index1[0][0]:index2[0][0]] # signal with background substacted _, signal_b, background = remove_shirley_background( signal, energy, 3, 1e-6) ###################### # set up model components and set constraints for the fit ###################### pre11 = 'Sb3d_32_1_' mod11 = PseudoVoigtModel(prefix=pre11) pars = mod11.make_params() pars.add('delta', value=10, min=9, max=11) # spin-split value, should be 9.4 eV pars.add('delta2', value=-1, min=-2.2, max=-0.9) # chemical shift between the two Sb species pars[pre11 + 'amplitude'].set(9000, min=0, max=100000) pars[pre11 + 'center'].set(-535.65, min=-536, max=-535.5) pars[pre11 + 'sigma'].set(0.3, min=0.2, max=1) pars[pre11 + 'fraction'].set(0.5, min=0.2, max=1) pre12 = 'Sb3d_32_2_' mod12 = PseudoVoigtModel(prefix=pre12) pars.update(mod12.make_params()) pars[pre12 + 'amplitude'].set(2050, min=0, max=100000) pars[pre12 + 'center'].set(expr='Sb3d_32_1_center+delta2') # all have same sigma value pars[pre12 + 'sigma'].set(expr='1.0*Sb3d_32_1_sigma') pars[pre12 + 'fraction'].set(expr='1.0*Sb3d_32_1_fraction') mod21 = PseudoVoigtModel(prefix='Sb3d_52_1_') pars.update(mod21.make_params()) pars['Sb3d_52_1_amplitude'].set(expr='3/2*Sb3d_32_1_amplitude') # same distance between 3/2 and 5/2 peaks pars['Sb3d_52_1_center'].set(expr='Sb3d_32_1_center+delta') # all have same sigma value # pars['Sb3d_52_1_sigma' ].set(expr='1.0*Sb3d_32_1_sigma') pars['Sb3d_52_1_fraction'].set(expr='1.0*Sb3d_32_1_fraction') mod22 = PseudoVoigtModel(prefix='Sb3d_52_2_') pars.update(mod22.make_params()) pars['Sb3d_52_2_amplitude'].set(expr='3/2*Sb3d_32_2_amplitude') # same distance between 3/2 and 5/2 peaks pars['Sb3d_52_2_center'].set(expr='Sb3d_32_2_center+delta') # all have same sigma value # pars['Sb3d_52_2_sigma' ].set(expr='1.0*Sb3d_32_1_sigma') pars['Sb3d_52_2_fraction'].set(expr='1.0*Sb3d_32_1_fraction') mod3 = GaussianModel(prefix='O1s_') pars.update(mod3.make_params()) pars['O1s_amplitude'].set(2050, min=0, max=500000) pars['O1s_center'].set(-531.9, min=-532.5, max=-531) pars['O1s_sigma'].set(1.3, min=0.2, max=2) # composite model is a sum of the components mod = mod11 + mod12 + mod21 + mod22 + mod3 # perform the fit out = mod.fit(signal_b, pars, x=energy) if doplot: fig = plt.figure(figsize=(6, 4)) # plot individual components comps = mod.eval_components(params=out.params, x=energy) labels = ('Sb 3d 3/2 1', 'Sb 3d 3/2 2', 'Sb 3d 5/2 1', 'Sb 3d 5/2 2', 'O 1s') for i, key in enumerate(comps): plt.plot(-energy, comps[key] / 10**3, color=COLORS[i], label=labels[i]) plt.fill_between(-energy, 0.0, comps[key] / 10**3, facecolor=COLORS[i], alpha=0.5) # plot measured signal (dots) and best fit (sum of components) plt.plot(-energy, signal_b / 10**3, 'k.') plt.plot(-energy, signal / 10**3, 'k-') plt.plot(-energy, background / 10**3, 'k--') plt.plot(-energy, out.best_fit / 10**3, color=COLORS[1]) plt.legend() ax = plt.gca() ax.set_xlim(540, 520) ax.xaxis.set_ticks(np.arange(519, 540, 5.0)) plt.xlabel('binding energy (eV)') plt.ylabel('counts ($10^3$/s)') if title is not None: plt.title(title) plt.savefig('Sb3d-fits/Sb3d-fit-%s.pdf' % title, dpi=600, bbox_inches='tight') #plt.close(fig) # fi # ### figures below are for debugging ### # plt.figure(33) # plt.plot(energy, signal_b/np.max(signal_b)) # plt.figure(34) # plt.plot(energy, out.best_fit/np.max(out.best_fit)) # plt.figure(35) # plt.plot(energy, signal_b/np.max(signal_b) - out.best_fit/np.max(out.best_fit)) # # comps = mod.eval_components(params=out.params, x=energy) # plt.figure(36) # a = comps['Sb3d_32_1_'] # b = comps['Sb3d_52_1_'] # c = comps['Sb3d_32_2_'] # d = comps['Sb3d_52_2_'] # maxpeak = np.max((np.amax(a), np.amax(b), np.amax(c), np.amax(d))) # plt.plot(energy, (a+b) / maxpeak) # # plt.figure(37) # plt.plot(energy, (c+d) / maxpeak) # print(out.fit_report(min_correl=0.25)) return (name, out.best_values, np.sum(np.abs(signal_b - out.best_fit)))
def fit_two_Psudo_Voigt(x_lst,y_lst,x_min_flt,x_max_flt,print_all_fits_bool,place_to_save_str): ''' x_lst = x axis y_lst = spectra to fit first = beginning of fitting regions last = end of fitting region print_all_fits = Bool, do you want to save all plots place_to_save = string that is the filename where we're saving the data This takes the spectra and fits two Lorentzian curves to it. Returns dictionary of fit values Parameters have prefixes "one" for first V, "two" for second V, "c" for constant ''' import numpy as np # for smoothing the curves import scipy.interpolate as interp #import splev from lmfit.models import PseudoVoigtModel, ConstantModel # Restrict the fit x_fit = [] y_fit = [] for x,y in zip(x_lst, y_lst): if x_min_flt < x < x_max_flt: x_fit.append(float(x)) y_fit.append(float(y)) x_fit = np.asarray(x_fit) y_fit = np.asarray(y_fit) # now we find the parameters using the - d^2/dx^2 ysmooth = interp.interp1d(x_fit, y_fit, kind='cubic') # differentiate x 2 yp = np.gradient(ysmooth(x_fit)) ypp = np.gradient(yp) # we want the peaks of -d2/dx2 ypp = np.asarray([-x for x in ypp]) ''' ******************************************************* Section of bad code that it'd take too long to do right ******************************************************* ''' # % of wavelength you want the peak centers to move wiggle_room = .05 w_guess = 3 # sigma pref1 = 'one' pref2 = 'two' prefo = 'off' # if the fancy shit doesn't work, this is how far in index # we shift the 2nd peak and max over doesnt_work_shift = 10 ''' ******************************************************* Section of bad code that it'd take too long to do right ******************************************************* ''' # this is the money # defines the model that'll be fit peak1 = PseudoVoigtModel(prefix = pref1, independent_vars=['x'],nan_policy='raise') peak2 = PseudoVoigtModel(prefix = pref2, independent_vars=['x'],nan_policy='raise') offset = ConstantModel(prefix=prefo, independent_vars=['x'],nan_policy='raise') mod = peak1 + peak2 + offset # guess parameters x_max = x_fit[np.argmax(ypp)] y_max = y_fit[np.argmax(ypp)] # peak #1 # here we set up the peak fitting guess. Then the peak fitter will make a parameter object out of them mod.set_param_hint(pref1+'amplitude', value = 5*y_max, min=y_max*.8,max = y_max*6,vary=True) mod.set_param_hint(pref1+'center', value = x_max, min = x_max*(1-wiggle_room), max = x_max*(1+wiggle_room),vary=True) mod.set_param_hint(pref1+'sigma', value = w_guess, min = 0, max = 5*w_guess,vary=True) # Set Fraction mod.set_param_hint(pref1+'fraction', value = .5, min = 0, max = 1,vary=True) # Change gama maybe # mod.set_param_hint(pref1+'gamma', value = 1, vary=True) # peak #2 x_trunk = [] y_trunk = [] ypp_trunk = [] try: for a,b,c in zip(x_fit.tolist(),y_fit.tolist(),ypp.tolist()): ''' BAD CODE MAKE THIS BETTER ''' if x_max + 8 < a < x_max + 12: x_trunk.append(a) y_trunk.append(b) ypp_trunk.append(c) x_trunk = np.asarray(x_trunk) y_trunk = np.asarray(y_trunk) ypp_trunk = np.asarray(ypp_trunk) x_max_2 = x_trunk[np.argmax(ypp_trunk)] y_max_2 = y_trunk[np.argmax(ypp_trunk)] except ValueError: x_max_2 = x_trunk[np.argmax(ypp) + doesnt_work_shift] y_max_2 = y_trunk[np.argmax(ypp) + doesnt_work_shift] # add peak 2 paramaters mod.set_param_hint(pref2+'amplitude', value = 4*y_max_2, min=y_max_2*.8,max = y_max_2*6,vary=True) # changed the bounds to be near other peak mod.set_param_hint(pref2+'center', value = x_max_2, min = x_max+8, max = x_max+14,vary=True) mod.set_param_hint(pref2+'sigma', value = w_guess/2, min = 0, max = w_guess ,vary=True) #mod.set_param_hint(pref2+'sigma', pref1 + 'sigma' < expr < pref1 + 'sigma') # Set Fraction mod.set_param_hint(pref2+'fraction', value = .5, min = 0, max = 1,vary=True) # Change gama maybe # mod.set_param_hint(pref2+'gamma', value = 1, vary=False) # constant offest mod.set_param_hint(prefo+'c', value = y_fit[-1], min = 0, max = 5*y_fit[-1],vary=False) # this does the fitting # the params = mod.ma... is what initializes the parameters result = mod.fit(y_fit, x=x_fit, params = mod.make_params()) # If print all fits ... if print_all_fits_bool: x_dense = np.arange(x_min_flt,x_max_flt,(x_max_flt-x_min_flt)/300.0).tolist() result.plot_fit(xlabel='Inv Cm', ylabel='counts',datafmt = 'xb', numpoints=len(x_fit)*10) ''' Here we make paramaters for peak 1 and 2 ''' for x in result.best_values: if pref1 in x: peak1.set_param_hint(x, value = result.best_values[str(x)]) elif pref2 in x: peak2.set_param_hint(x, value = result.best_values[str(x)]) else: peak1.set_param_hint(x, value = result.best_values[str(x)]) peak2.set_param_hint(x, value = result.best_values[str(x)]) comp = [peak1.eval(x=yy, params=peak1.make_params()) for yy in x_dense] plt.plot(x_dense,comp, 'green', label = None) comp = [peak2.eval(x=yy, params=peak2.make_params()) for yy in x_dense] plt.plot(x_dense, comp, 'green', label= None) plt.title("Fit vs Data") plt.ylim(0, 1.1*np.max(y_fit)) plt.legend() plt.savefig(place_to_save_str) plt.clf() return result.best_values
def apply_old_model(x_data, y_data, peaks, plot_fits): """ This function is used within the `superimpose_set` function to apply and existing fit to the next spectra in a decomposition series. It helps to improve consistency of fits that do not vary signficantly between residence times. This avoids bad fits where the peak shape can vary significantly between residence times. Args: x_data (list like): The x-values of the spectra for which the model will be fit. y_data (list like): The y-values of the spectra for which the model will be fit. peaks (list): A list containing tuples of pseudo-Voigt decriptors for each peak. plot_fits (boolean): A simple True/False boolean input that determins if the plot_fit function should be used to display the resulting fit for visual inspection. Returns: fit_result (list): An array containing both the peak number, as well as the fraction Lorentzian character, sigma, center, amplitude, full-width, half-max, the height, and the area under the curve of the peaks. The data for peak i can be accessed by the array positions shown here: fit_result[i][0] = p[i]_fraction fit_result[i][1] = p[i]_simga fit_result[i][2] = p[i]_center fit_result[i][3] = p[i]_amplitude fit_result[i][4] = p[i]_fwhm fit_result[i][5] = p[i]_height fit_result[i][6] = p[i]_area under the curve residuals (): """ # handling errors in inputs if not isinstance(x_data, (list, np.ndarray)): raise TypeError( 'Passed value of `x_data` is not a list or numpy.ndarray! Instead, it is: ' + str(type(x_data))) if not isinstance(y_data, (list, np.ndarray)): raise TypeError( 'Passed value of `y_data` is not a list or numpy.ndarray! Instead, it is: ' + str(type(y_data))) if not isinstance(peaks, list): raise TypeError( 'Passed value of `peaks` is not a list! Instead, it is: ' + str(type(peaks))) if not isinstance(plot_fits, bool): raise TypeError( 'Passed value of `plot_fits` is not a boolean! Instead, it is: ' + str(type(plot_fits))) # add old peaks to the model old_peak_list = [] for i, old_peak in enumerate(peaks): prefix = 'p{}_'.format(i + 1) peak = PseudoVoigtModel(prefix=prefix) if i == 0: pars = peak.make_params() else: pars.update(peak.make_params()) pars[prefix + 'fraction'].set(old_peak[0]) pars[prefix + 'center'].set(old_peak[2], vary=True, min=(old_peak[2] - 10), max=(old_peak[2] + 10)) pars[prefix + 'height'].set(min=0.1 * old_peak[5]) pars[prefix + 'sigma'].set(old_peak[1], min=0.9 * old_peak[1], max=1.1 * old_peak[1]) pars[prefix + 'amplitude'].set(old_peak[3], min=0) old_peak_list.append(peak) if i == 0: mod = old_peak_list[i] else: mod = mod + old_peak_list[i] # run the fit out = model_fit(x_data, y_data, mod, pars, report=False) # plot_fits option if plot_fits is True: plot_fit(x_data, y_data, out, plot_components=True) else: pass # save fit data fit_result, residuals = export_fit_data(x_data, y_data, out) # add 'user_added' label as 8th term to user added peaks for i in range(len(peaks), len(fit_result)): fit_result[i].append('user_added') # sort peaks by center location for saving fit_result = sorted(fit_result, key=lambda x: int(x[2])) return fit_result, residuals
def build_custom_model(x_data, y_data, peaks, peaks_add, plot_fits): """ This function is primarily utilized via the dataprep.adjust_peaks function. It allows a custom lmfit.model.CompositeModel to be generated using a list of accepted peak values and a list of new peak values to be added based on user experience and expertise. User input peaks are slightly less constrained than those automatically detected using local maximia and therefore their center (wavenumber) value may shift slighty to optimize the fit. Args: x_data (list like): The x-values of the spectra from which peaks will be detected. y_data (list like): The y-values of the spectra from which peaks will be detected. peaks (list): A list containing tuples of the x_data (wavenumber) and y_data (counts) values of the peaks. peaks_add (list): A list of tuples containing user specified peak locations to be added to the fit as well as interpolated values to provide an initial height guess. plot_fits (boolean): A simple True/False boolean input that determins if the plot_fit function should be used to display the resulting fit for visual inspection. Returns: fit_result (list): An array containing both the peak number, as well as the fraction Lorentzian character, sigma, center, amplitude, full-width, half-max, and the height of the peaks. The data for peak i can be accessed by the array positions shown here: fit_result[i][0] = p[i]_fraction fit_result[i][1] = p[i]_simga fit_result[i][2] = p[i]_center fit_result[i][3] = p[i]_amplitude fit_result[i][4] = p[i]_fwhm fit_result[i][5] = p[i]_height fit_result[i][6] = p[i]_area under the curve """ # handling errors in inputs if not isinstance(x_data, (list, np.ndarray)): raise TypeError( 'Passed value of `x_data` is not a list or numpy.ndarray! Instead, it is: ' + str(type(x_data))) if not isinstance(y_data, (list, np.ndarray)): raise TypeError( 'Passed value of `y_data` is not a list or numpy.ndarray! Instead, it is: ' + str(type(y_data))) if not isinstance(peaks, list): raise TypeError( 'Passed value of `peaks` is not a list! Instead, it is: ' + str(type(peaks))) if not isinstance(peaks_add, list): raise TypeError( 'Passed value of `peaks_add` is not a list! Instead, it is: ' + str(type(peaks_add))) if not isinstance(plot_fits, bool): raise TypeError( 'Passed value of `plot_fits` is not a boolean! Instead, it is: ' + str(type(plot_fits))) # add new list of peaks to model # first starting with existing peaks old_peak_list = [] for i, old_peak in enumerate(peaks): prefix = 'p{}_'.format(i + 1) peak = PseudoVoigtModel(prefix=prefix) if i == 0: pars = peak.make_params() else: pars.update(peak.make_params()) pars[prefix + 'fraction'].set(old_peak[0]) pars[prefix + 'center'].set(old_peak[2], vary=True, min=(old_peak[2] - 10), max=(old_peak[2] + 10)) pars[prefix + 'height'].set(min=0.1 * old_peak[5]) pars[prefix + 'sigma'].set(old_peak[1], min=1, max=150) pars[prefix + 'amplitude'].set(old_peak[3], min=0) old_peak_list.append(peak) if i == 0: mod = old_peak_list[i] else: mod = mod + old_peak_list[i] # then add new peaks with intial guesses new_peak_list = [] for i, add_peak in enumerate(peaks_add): prefix = 'p{}_'.format(i + 1 + len(peaks)) peak = PseudoVoigtModel(prefix=prefix) pars.update(peak.make_params()) pars[prefix + 'center'].set(add_peak[0], vary=True, min=(add_peak[0] - 10), max=(add_peak[0] + 10)) pars[prefix + 'height'].set(min=0.1 * add_peak[1]) pars[prefix + 'sigma'].set(10, min=1, max=150) pars[prefix + 'amplitude'].set(20 * add_peak[1], min=0) new_peak_list.append(peak) mod = mod + new_peak_list[i] # run the fit out = model_fit(x_data, y_data, mod, pars, report=False) # plot_fits option if plot_fits is True: plot_fit(x_data, y_data, out, plot_components=True) else: pass # save fit data fit_result, residuals = export_fit_data(x_data, y_data, out) # add 'user_added' label as 8th term to user added peaks for i in range(len(peaks), len(fit_result)): fit_result[i].append('user_added') # sort peaks by center location for saving fit_result = sorted(fit_result, key=lambda x: int(x[2])) return fit_result, residuals