Coverage for peakipy/lineshapes.py: 90%
143 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-14 14:49 -0400
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-14 14:49 -0400
1from enum import Enum
3import pandas as pd
4from numpy import sqrt, exp, log
5from scipy.special import wofz
7from peakipy.constants import π, tiny, log2
10class Lineshape(str, Enum):
11 PV = "PV"
12 V = "V"
13 G = "G"
14 L = "L"
15 PV_PV = "PV_PV"
16 G_L = "G_L"
17 PV_G = "PV_G"
18 PV_L = "PV_L"
21def gaussian(x, center=0.0, sigma=1.0):
22 r"""1-dimensional Gaussian function.
24 gaussian(x, center, sigma) =
25 (1/(s2pi*sigma)) * exp(-(1.0*x-center)**2 / (2*sigma**2))
27 :math:`\\frac{1}{ \sqrt{2\pi} } exp \left( \\frac{-(x-center)^2}{2 \sigma^2} \\right)`
29 :param x: x
30 :param center: center
31 :param sigma: sigma
32 :type x: numpy.array
33 :type center: float
34 :type sigma: float
36 :return: 1-dimensional Gaussian
37 :rtype: numpy.array
39 """
40 return (1.0 / max(tiny, (sqrt(2 * π) * sigma))) * exp(
41 -((1.0 * x - center) ** 2) / max(tiny, (2 * sigma**2))
42 )
45def lorentzian(x, center=0.0, sigma=1.0):
46 r"""1-dimensional Lorentzian function.
48 lorentzian(x, center, sigma) =
49 (1/(1 + ((1.0*x-center)/sigma)**2)) / (pi*sigma)
51 :math:`\\frac{1}{ 1+ \left( \\frac{x-center}{\sigma}\\right)^2} / (\pi\sigma)`
53 :param x: x
54 :param center: center
55 :param sigma: sigma
56 :type x: numpy.array
57 :type center: float
58 :type sigma: float
60 :return: 1-dimensional Lorenztian
61 :rtype: numpy.array
63 """
64 return (1.0 / (1 + ((1.0 * x - center) / max(tiny, sigma)) ** 2)) / max(
65 tiny, (π * sigma)
66 )
69def voigt(x, center=0.0, sigma=1.0, gamma=None):
70 r"""Return a 1-dimensional Voigt function.
72 voigt(x, center, sigma, gamma) =
73 amplitude*wofz(z).real / (sigma*sqrt(2.0 * π))
75 :math:`V(x,\sigma,\gamma) = (\\frac{Re[\omega(z)]}{\sigma \sqrt{2\pi}})`
77 :math:`z=\\frac{x+i\gamma}{\sigma\sqrt{2}}`
79 see Voigt_ wiki
81 .. _Voigt: https://en.wikipedia.org/wiki/Voigt_profile
84 :param x: x values
85 :type x: numpy array 1d
86 :param center: center of lineshape in points
87 :type center: float
88 :param sigma: sigma of gaussian
89 :type sigma: float
90 :param gamma: gamma of lorentzian
91 :type gamma: float
93 :returns: Voigt lineshape
94 :rtype: numpy.array
96 """
97 if gamma is None:
98 gamma = sigma
100 z = (x - center + 1j * gamma) / max(tiny, (sigma * sqrt(2.0)))
101 return wofz(z).real / max(tiny, (sigma * sqrt(2.0 * π)))
104def pseudo_voigt(x, center=0.0, sigma=1.0, fraction=0.5):
105 r"""1-dimensional Pseudo-voigt function
107 Superposition of Gaussian and Lorentzian function
109 :math:`(1-\phi) G(x,center,\sigma_g) + \phi L(x, center, \sigma)`
111 Where :math:`\phi` is the fraction of Lorentzian lineshape and :math:`G` and :math:`L` are Gaussian and
112 Lorentzian functions, respectively.
114 :param x: data
115 :type x: numpy.array
116 :param center: center of peak
117 :type center: float
118 :param sigma: sigma of lineshape
119 :type sigma: float
120 :param fraction: fraction of lorentzian lineshape (between 0 and 1)
121 :type fraction: float
123 :return: pseudo-voigt function
124 :rtype: numpy.array
126 """
127 sigma_g = sigma / sqrt(2 * log2)
128 pv = (1 - fraction) * gaussian(x, center, sigma_g) + fraction * lorentzian(
129 x, center, sigma
130 )
131 return pv
134def pvoigt2d(
135 XY,
136 amplitude=1.0,
137 center_x=0.5,
138 center_y=0.5,
139 sigma_x=1.0,
140 sigma_y=1.0,
141 fraction=0.5,
142):
143 r"""2D pseudo-voigt model
145 :math:`(1-fraction) G(x,center,\sigma_{gx}) + (fraction) L(x, center, \sigma_x) * (1-fraction) G(y,center,\sigma_{gy}) + (fraction) L(y, center, \sigma_y)`
147 :param XY: meshgrid of X and Y coordinates [X,Y] each with shape Z
148 :type XY: numpy.array
150 :param amplitude: amplitude of peak
151 :type amplitude: float
153 :param center_x: center of peak in x
154 :type center_x: float
156 :param center_y: center of peak in x
157 :type center_y: float
159 :param sigma_x: sigma of lineshape in x
160 :type sigma_x: float
162 :param sigma_y: sigma of lineshape in y
163 :type sigma_y: float
165 :param fraction: fraction of lorentzian lineshape (between 0 and 1)
166 :type fraction: float
168 :return: flattened array of Z values (use Z.reshape(X.shape) for recovery)
169 :rtype: numpy.array
171 """
172 x, y = XY
173 pv_x = pseudo_voigt(x, center_x, sigma_x, fraction)
174 pv_y = pseudo_voigt(y, center_y, sigma_y, fraction)
175 return amplitude * pv_x * pv_y
178def pv_l(
179 XY,
180 amplitude=1.0,
181 center_x=0.5,
182 center_y=0.5,
183 sigma_x=1.0,
184 sigma_y=1.0,
185 fraction=0.5,
186):
187 """2D lineshape model with pseudo-voigt in x and lorentzian in y
189 Arguments
190 =========
192 -- XY: meshgrid of X and Y coordinates [X,Y] each with shape Z
193 -- amplitude: peak amplitude (gaussian and lorentzian)
194 -- center_x: position of peak in x
195 -- center_y: position of peak in y
196 -- sigma_x: linewidth in x
197 -- sigma_y: linewidth in y
198 -- fraction: fraction of lorentzian in fit
200 Returns
201 =======
203 -- flattened array of Z values (use Z.reshape(X.shape) for recovery)
205 """
207 x, y = XY
208 pv_x = pseudo_voigt(x, center_x, sigma_x, fraction)
209 pv_y = pseudo_voigt(y, center_y, sigma_y, 1.0) # lorentzian
210 return amplitude * pv_x * pv_y
213def pv_g(
214 XY,
215 amplitude=1.0,
216 center_x=0.5,
217 center_y=0.5,
218 sigma_x=1.0,
219 sigma_y=1.0,
220 fraction=0.5,
221):
222 """2D lineshape model with pseudo-voigt in x and gaussian in y
224 Arguments
225 ---------
227 -- XY: meshgrid of X and Y coordinates [X,Y] each with shape Z
228 -- amplitude: peak amplitude (gaussian and lorentzian)
229 -- center_x: position of peak in x
230 -- center_y: position of peak in y
231 -- sigma_x: linewidth in x
232 -- sigma_y: linewidth in y
233 -- fraction: fraction of lorentzian in fit
235 Returns
236 -------
238 -- flattened array of Z values (use Z.reshape(X.shape) for recovery)
240 """
241 x, y = XY
242 pv_x = pseudo_voigt(x, center_x, sigma_x, fraction)
243 pv_y = pseudo_voigt(y, center_y, sigma_y, 0.0) # gaussian
244 return amplitude * pv_x * pv_y
247def pv_pv(
248 XY,
249 amplitude=1.0,
250 center_x=0.5,
251 center_y=0.5,
252 sigma_x=1.0,
253 sigma_y=1.0,
254 fraction_x=0.5,
255 fraction_y=0.5,
256):
257 """2D lineshape model with pseudo-voigt in x and pseudo-voigt in y
258 i.e. fraction_x and fraction_y params
260 Arguments
261 =========
263 -- XY: meshgrid of X and Y coordinates [X,Y] each with shape Z
264 -- amplitude: peak amplitude (gaussian and lorentzian)
265 -- center_x: position of peak in x
266 -- center_y: position of peak in y
267 -- sigma_x: linewidth in x
268 -- sigma_y: linewidth in y
269 -- fraction_x: fraction of lorentzian in x
270 -- fraction_y: fraction of lorentzian in y
272 Returns
273 =======
275 -- flattened array of Z values (use Z.reshape(X.shape) for recovery)
277 """
279 x, y = XY
280 pv_x = pseudo_voigt(x, center_x, sigma_x, fraction_x)
281 pv_y = pseudo_voigt(y, center_y, sigma_y, fraction_y)
282 return amplitude * pv_x * pv_y
285def gaussian_lorentzian(
286 XY,
287 amplitude=1.0,
288 center_x=0.5,
289 center_y=0.5,
290 sigma_x=1.0,
291 sigma_y=1.0,
292 fraction=0.5,
293):
294 """2D lineshape model with gaussian in x and lorentzian in y
296 Arguments
297 =========
299 -- XY: meshgrid of X and Y coordinates [X,Y] each with shape Z
300 -- amplitude: peak amplitude (gaussian and lorentzian)
301 -- center_x: position of peak in x
302 -- center_y: position of peak in y
303 -- sigma_x: linewidth in x
304 -- sigma_y: linewidth in y
305 -- fraction: fraction of lorentzian in fit
307 Returns
308 =======
310 -- flattened array of Z values (use Z.reshape(X.shape) for recovery)
312 """
313 x, y = XY
314 pv_x = pseudo_voigt(x, center_x, sigma_x, 0.0) # gaussian
315 pv_y = pseudo_voigt(y, center_y, sigma_y, 1.0) # lorentzian
316 return amplitude * pv_x * pv_y
319def voigt2d(
320 XY,
321 amplitude=1.0,
322 center_x=0.5,
323 center_y=0.5,
324 sigma_x=1.0,
325 sigma_y=1.0,
326 gamma_x=1.0,
327 gamma_y=1.0,
328 fraction=0.5,
329):
330 fraction = 0.5
331 gamma_x = None
332 gamma_y = None
333 x, y = XY
334 voigt_x = voigt(x, center_x, sigma_x, gamma_x)
335 voigt_y = voigt(y, center_y, sigma_y, gamma_y)
336 return amplitude * voigt_x * voigt_y
339def get_lineshape_function(lineshape: Lineshape):
340 match lineshape:
341 case lineshape.PV | lineshape.G | lineshape.L:
342 lineshape_function = pvoigt2d
343 case lineshape.V:
344 lineshape_function = voigt2d
345 case lineshape.PV_PV:
346 lineshape_function = pv_pv
347 case lineshape.G_L:
348 lineshape_function = gaussian_lorentzian
349 case lineshape.PV_G:
350 lineshape_function = pv_g
351 case lineshape.PV_L:
352 lineshape_function = pv_l
353 case _:
354 raise Exception("No lineshape was selected!")
355 return lineshape_function
358def calculate_height_for_voigt_lineshape(df):
359 df["height"] = df.apply(
360 lambda x: voigt2d(
361 XY=[0, 0],
362 center_x=0.0,
363 center_y=0.0,
364 sigma_x=x.sigma_x,
365 sigma_y=x.sigma_y,
366 gamma_x=x.gamma_x,
367 gamma_y=x.gamma_y,
368 amplitude=x.amp,
369 ),
370 axis=1,
371 )
372 df["height_err"] = df.apply(
373 lambda x: x.amp_err * (x.height / x.amp) if x.amp_err != None else 0.0,
374 axis=1,
375 )
376 return df
379def calculate_fwhm_for_voigt_lineshape(df):
380 df["fwhm_g_x"] = df.sigma_x.apply(
381 lambda x: 2.0 * x * sqrt(2.0 * log(2.0))
382 ) # fwhm of gaussian
383 df["fwhm_g_y"] = df.sigma_y.apply(lambda x: 2.0 * x * sqrt(2.0 * log(2.0)))
384 df["fwhm_l_x"] = df.gamma_x.apply(lambda x: 2.0 * x) # fwhm of lorentzian
385 df["fwhm_l_y"] = df.gamma_y.apply(lambda x: 2.0 * x)
386 df["fwhm_x"] = df.apply(
387 lambda x: 0.5346 * x.fwhm_l_x
388 + sqrt(0.2166 * x.fwhm_l_x**2.0 + x.fwhm_g_x**2.0),
389 axis=1,
390 )
391 df["fwhm_y"] = df.apply(
392 lambda x: 0.5346 * x.fwhm_l_y
393 + sqrt(0.2166 * x.fwhm_l_y**2.0 + x.fwhm_g_y**2.0),
394 axis=1,
395 )
396 return df
399def calculate_height_for_pseudo_voigt_lineshape(df):
400 df["height"] = df.apply(
401 lambda x: pvoigt2d(
402 XY=[0, 0],
403 center_x=0.0,
404 center_y=0.0,
405 sigma_x=x.sigma_x,
406 sigma_y=x.sigma_y,
407 amplitude=x.amp,
408 fraction=x.fraction,
409 ),
410 axis=1,
411 )
412 df["height_err"] = df.apply(lambda x: x.amp_err * (x.height / x.amp), axis=1)
413 return df
416def calculate_fwhm_for_pseudo_voigt_lineshape(df):
417 df["fwhm_x"] = df.sigma_x.apply(lambda x: x * 2.0)
418 df["fwhm_y"] = df.sigma_y.apply(lambda x: x * 2.0)
419 return df
422def calculate_height_for_gaussian_lineshape(df):
423 df["height"] = df.apply(
424 lambda x: pvoigt2d(
425 XY=[0, 0],
426 center_x=0.0,
427 center_y=0.0,
428 sigma_x=x.sigma_x,
429 sigma_y=x.sigma_y,
430 amplitude=x.amp,
431 fraction=0.0, # gaussian
432 ),
433 axis=1,
434 )
435 df["height_err"] = df.apply(lambda x: x.amp_err * (x.height / x.amp), axis=1)
436 return df
439def calculate_height_for_lorentzian_lineshape(df):
440 df["height"] = df.apply(
441 lambda x: pvoigt2d(
442 XY=[0, 0],
443 center_x=0.0,
444 center_y=0.0,
445 sigma_x=x.sigma_x,
446 sigma_y=x.sigma_y,
447 amplitude=x.amp,
448 fraction=1.0, # lorentzian
449 ),
450 axis=1,
451 )
452 df["height_err"] = df.apply(lambda x: x.amp_err * (x.height / x.amp), axis=1)
453 return df
456def calculate_height_for_pv_pv_lineshape(df):
457 df["height"] = df.apply(
458 lambda x: pv_pv(
459 XY=[0, 0],
460 center_x=0.0,
461 center_y=0.0,
462 sigma_x=x.sigma_x,
463 sigma_y=x.sigma_y,
464 amplitude=x.amp,
465 fraction_x=x.fraction_x,
466 fraction_y=x.fraction_y,
467 ),
468 axis=1,
469 )
470 df["height_err"] = df.apply(lambda x: x.amp_err * (x.height / x.amp), axis=1)
471 return df
474def calculate_peak_centers_in_ppm(df, peakipy_data):
475 # convert values to ppm
476 df["center_x_ppm"] = df.center_x.apply(lambda x: peakipy_data.uc_f2.ppm(x))
477 df["center_y_ppm"] = df.center_y.apply(lambda x: peakipy_data.uc_f1.ppm(x))
478 df["init_center_x_ppm"] = df.init_center_x.apply(
479 lambda x: peakipy_data.uc_f2.ppm(x)
480 )
481 df["init_center_y_ppm"] = df.init_center_y.apply(
482 lambda x: peakipy_data.uc_f1.ppm(x)
483 )
484 return df
487def calculate_peak_linewidths_in_hz(df, peakipy_data):
488 df["sigma_x_ppm"] = df.sigma_x.apply(lambda x: x * peakipy_data.ppm_per_pt_f2)
489 df["sigma_y_ppm"] = df.sigma_y.apply(lambda x: x * peakipy_data.ppm_per_pt_f1)
490 df["fwhm_x_ppm"] = df.fwhm_x.apply(lambda x: x * peakipy_data.ppm_per_pt_f2)
491 df["fwhm_y_ppm"] = df.fwhm_y.apply(lambda x: x * peakipy_data.ppm_per_pt_f1)
492 df["fwhm_x_hz"] = df.fwhm_x.apply(lambda x: x * peakipy_data.hz_per_pt_f2)
493 df["fwhm_y_hz"] = df.fwhm_y.apply(lambda x: x * peakipy_data.hz_per_pt_f1)
494 return df
497def calculate_lineshape_specific_height_and_fwhm(
498 lineshape: Lineshape, df: pd.DataFrame
499):
500 match lineshape:
501 case lineshape.V:
502 df = calculate_height_for_voigt_lineshape(df)
503 df = calculate_fwhm_for_voigt_lineshape(df)
505 case lineshape.PV:
506 df = calculate_height_for_pseudo_voigt_lineshape(df)
507 df = calculate_fwhm_for_pseudo_voigt_lineshape(df)
509 case lineshape.G:
510 df = calculate_height_for_gaussian_lineshape(df)
511 df = calculate_fwhm_for_pseudo_voigt_lineshape(df)
513 case lineshape.L:
514 df = calculate_height_for_lorentzian_lineshape(df)
515 df = calculate_fwhm_for_pseudo_voigt_lineshape(df)
517 case lineshape.PV_PV:
518 df = calculate_height_for_pv_pv_lineshape(df)
519 df = calculate_fwhm_for_pseudo_voigt_lineshape(df)
520 case _:
521 df = calculate_fwhm_for_pseudo_voigt_lineshape(df)
522 return df