Coverage for peakipy/lineshapes.py: 90%

143 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-09-14 14:49 -0400

1from enum import Enum 

2 

3import pandas as pd 

4from numpy import sqrt, exp, log 

5from scipy.special import wofz 

6 

7from peakipy.constants import π, tiny, log2 

8 

9 

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" 

19 

20 

21def gaussian(x, center=0.0, sigma=1.0): 

22 r"""1-dimensional Gaussian function. 

23 

24 gaussian(x, center, sigma) = 

25 (1/(s2pi*sigma)) * exp(-(1.0*x-center)**2 / (2*sigma**2)) 

26 

27 :math:`\\frac{1}{ \sqrt{2\pi} } exp \left( \\frac{-(x-center)^2}{2 \sigma^2} \\right)` 

28 

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 

35 

36 :return: 1-dimensional Gaussian 

37 :rtype: numpy.array 

38 

39 """ 

40 return (1.0 / max(tiny, (sqrt(2 * π) * sigma))) * exp( 

41 -((1.0 * x - center) ** 2) / max(tiny, (2 * sigma**2)) 

42 ) 

43 

44 

45def lorentzian(x, center=0.0, sigma=1.0): 

46 r"""1-dimensional Lorentzian function. 

47 

48 lorentzian(x, center, sigma) = 

49 (1/(1 + ((1.0*x-center)/sigma)**2)) / (pi*sigma) 

50 

51 :math:`\\frac{1}{ 1+ \left( \\frac{x-center}{\sigma}\\right)^2} / (\pi\sigma)` 

52 

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 

59 

60 :return: 1-dimensional Lorenztian 

61 :rtype: numpy.array 

62 

63 """ 

64 return (1.0 / (1 + ((1.0 * x - center) / max(tiny, sigma)) ** 2)) / max( 

65 tiny, (π * sigma) 

66 ) 

67 

68 

69def voigt(x, center=0.0, sigma=1.0, gamma=None): 

70 r"""Return a 1-dimensional Voigt function. 

71 

72 voigt(x, center, sigma, gamma) = 

73 amplitude*wofz(z).real / (sigma*sqrt(2.0 * π)) 

74 

75 :math:`V(x,\sigma,\gamma) = (\\frac{Re[\omega(z)]}{\sigma \sqrt{2\pi}})` 

76 

77 :math:`z=\\frac{x+i\gamma}{\sigma\sqrt{2}}` 

78 

79 see Voigt_ wiki 

80 

81 .. _Voigt: https://en.wikipedia.org/wiki/Voigt_profile 

82 

83 

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 

92 

93 :returns: Voigt lineshape 

94 :rtype: numpy.array 

95 

96 """ 

97 if gamma is None: 

98 gamma = sigma 

99 

100 z = (x - center + 1j * gamma) / max(tiny, (sigma * sqrt(2.0))) 

101 return wofz(z).real / max(tiny, (sigma * sqrt(2.0 * π))) 

102 

103 

104def pseudo_voigt(x, center=0.0, sigma=1.0, fraction=0.5): 

105 r"""1-dimensional Pseudo-voigt function 

106 

107 Superposition of Gaussian and Lorentzian function 

108 

109 :math:`(1-\phi) G(x,center,\sigma_g) + \phi L(x, center, \sigma)` 

110 

111 Where :math:`\phi` is the fraction of Lorentzian lineshape and :math:`G` and :math:`L` are Gaussian and 

112 Lorentzian functions, respectively. 

113 

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 

122 

123 :return: pseudo-voigt function 

124 :rtype: numpy.array 

125 

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 

132 

133 

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 

144 

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

146 

147 :param XY: meshgrid of X and Y coordinates [X,Y] each with shape Z 

148 :type XY: numpy.array 

149 

150 :param amplitude: amplitude of peak 

151 :type amplitude: float 

152 

153 :param center_x: center of peak in x 

154 :type center_x: float 

155 

156 :param center_y: center of peak in x 

157 :type center_y: float 

158 

159 :param sigma_x: sigma of lineshape in x 

160 :type sigma_x: float 

161 

162 :param sigma_y: sigma of lineshape in y 

163 :type sigma_y: float 

164 

165 :param fraction: fraction of lorentzian lineshape (between 0 and 1) 

166 :type fraction: float 

167 

168 :return: flattened array of Z values (use Z.reshape(X.shape) for recovery) 

169 :rtype: numpy.array 

170 

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 

176 

177 

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 

188 

189 Arguments 

190 ========= 

191 

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 

199 

200 Returns 

201 ======= 

202 

203 -- flattened array of Z values (use Z.reshape(X.shape) for recovery) 

204 

205 """ 

206 

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 

211 

212 

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 

223 

224 Arguments 

225 --------- 

226 

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 

234 

235 Returns 

236 ------- 

237 

238 -- flattened array of Z values (use Z.reshape(X.shape) for recovery) 

239 

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 

245 

246 

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 

259 

260 Arguments 

261 ========= 

262 

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 

271 

272 Returns 

273 ======= 

274 

275 -- flattened array of Z values (use Z.reshape(X.shape) for recovery) 

276 

277 """ 

278 

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 

283 

284 

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 

295 

296 Arguments 

297 ========= 

298 

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 

306 

307 Returns 

308 ======= 

309 

310 -- flattened array of Z values (use Z.reshape(X.shape) for recovery) 

311 

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 

317 

318 

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 

337 

338 

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 

356 

357 

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 

377 

378 

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 

397 

398 

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 

414 

415 

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 

420 

421 

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 

437 

438 

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 

454 

455 

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 

472 

473 

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 

485 

486 

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 

495 

496 

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) 

504 

505 case lineshape.PV: 

506 df = calculate_height_for_pseudo_voigt_lineshape(df) 

507 df = calculate_fwhm_for_pseudo_voigt_lineshape(df) 

508 

509 case lineshape.G: 

510 df = calculate_height_for_gaussian_lineshape(df) 

511 df = calculate_fwhm_for_pseudo_voigt_lineshape(df) 

512 

513 case lineshape.L: 

514 df = calculate_height_for_lorentzian_lineshape(df) 

515 df = calculate_fwhm_for_pseudo_voigt_lineshape(df) 

516 

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