Source code for TRXASprefitpack.mathfun.peak_shape

'''
peak_shape:
submodule for the mathematical functions for
peak shape function

:copyright: 2021-2022 by pistack (Junho Lee).
:license: LGPL3.
'''
from typing import Union, Optional
import numpy as np
from scipy.special import erf, wofz

[docs]def edge_gaussian(e: Union[float, np.ndarray], fwhm_G: float) -> np.ndarray: ''' Gaussian type edge function :math:(\\frac{1}{2}\\left(1+{erf}\\left(\\frac{x}{\\sigma\\sqrt{2}}\\right)\\right)) Args: e: energy fwhm_G: full width at half maximum Returns: gaussian type edge shape function ''' return (1+erf(2*np.sqrt(np.log(2))*e/fwhm_G))/2
[docs]def edge_lorenzian(e: Union[float, np.ndarray], fwhm_L: float) -> np.ndarray: ''' Lorenzian type edge function :math:(0.5+\\frac{1}{\\pi}{arctan}\\left(\\frac{x}{\\gamma}\\right)) Args: e: energy fwhm_L: full width at half maximum Returns: lorenzian type edge shape function ''' return 0.5+np.arctan(2*e/fwhm_L)/np.pi
[docs]def voigt(e: Union[float, np.ndarray], fwhm_G: float, fwhm_L: float) -> Union[float, np.ndarray]: ''' voigt: evaluates voigt profile function with full width at half maximum of gaussian part is fwhm_G and full width at half maximum of lorenzian part is fwhm_L Args: e: energy fwhm_G: full width at half maximum of gaussian part :math:`(2\\sqrt{2\\log(2)}\\sigma)` fwhm_L: full width at half maximum of lorenzian part :math:`(2\\gamma)` Returns: voigt profile Note: * if fwhm_G is (<1e-8) it returns normalized lorenzian shape * if fwhm_L is (<1e-8) it returns normalized gaussian shape ''' sigma = fwhm_G/(2*np.sqrt(2*np.log(2))) if fwhm_G < 1e-8: return fwhm_L/2/np.pi/(e**2+fwhm_L**2/4) if fwhm_L < 1e-8: return np.exp(-(e/sigma)**2/2)/(sigma*np.sqrt(2*np.pi)) z = (e+complex(0,fwhm_L/2))/(sigma*np.sqrt(2)) return wofz(z).real/(sigma*np.sqrt(2*np.pi))
[docs]def voigt_thy(e: np.ndarray, thy_peak: np.ndarray, fwhm_G: float, fwhm_L: float, peak_factor: Union[float, np.ndarray], policy: Optional[str] = 'shift') -> np.ndarray: ''' Calculates normalized voigt broadened theoretically calculated lineshape spectrum Args: e: energy thy_peak: theoretical calculated peak position and intensity fwhm_G: full width at half maximum of gaussian shape fwhm_L: full width at half maximum of lorenzian shape peak_factor: Peak factor, its behavior depends on policy. policy ({'shift', 'scale', 'both'}): Policy to match discrepency between experimental data and theoretical spectrum. * 'shift' : Default option, shift peak position by peak_factor * 'scale' : scale peak position by peak_factor * 'both' : both shift and scale peak postition. `peak_factor` should be equal to the pair of `shift_factor` and `scale_factor`. Returns: normalized voigt broadened theoritical lineshape spectrum ''' v_matrix = np.empty((e.size, thy_peak.shape[0])) peak_copy = np.copy(thy_peak[:, 0]) if policy == 'shift': peak_copy = peak_copy + peak_factor elif policy == 'scale': peak_copy = peak_factor*peak_copy else: peak_copy = peak_factor[1]*peak_copy + peak_factor[0] for i in range(peak_copy.size): v_matrix[:, i] = voigt(e-peak_copy[i], fwhm_G, fwhm_L) broadened_theory = v_matrix @ thy_peak[:, 1].reshape((peak_copy.size, 1)) return broadened_theory.flatten()
[docs]def deriv_edge_gaussian(e: Union[float, np.ndarray], fwhm_G: float) -> np.ndarray: ''' derivative of gaussian type edge Args: e: energy fwhm_G: full width at half maximum Returns: first derivative of gaussian edge function Note: * 1st column: df/de * 2nd column: df/d(fwhm_G) ''' tmp = np.exp(-4*np.log(2)*(e/fwhm_G)**2)/np.sqrt(np.pi) grad_e = 2*np.sqrt(np.log(2))/fwhm_G*tmp grad_fwhm_G = -2*np.sqrt(np.log(2))*e/fwhm_G/fwhm_G*tmp if isinstance(e, np.ndarray): grad = np.empty((e.size, 2)) grad[:, 0] = grad_e grad[:, 1] = grad_fwhm_G else: grad = np.empty(2) grad[0] = grad_e grad[1] = grad_fwhm_G return grad
[docs]def deriv_edge_lorenzian(e: Union[float, np.ndarray], fwhm_L: float) -> np.ndarray: ''' derivative of lorenzian type edge Args: e: energy fwhm_G: full width at half maximum Returns: first derivative of lorenzian type function Note: * 1st column: df/de * 2nd column: df/d(fwhm_L) ''' tmp = 1/np.pi/(e**2+fwhm_L**2/4) grad_e = fwhm_L*tmp/2 grad_fwhm_L = -e*tmp/2 if isinstance(e, np.ndarray): grad = np.empty((e.size, 2)) grad[:, 0] = grad_e grad[:, 1] = grad_fwhm_L else: grad = np.empty(2) grad[0] = grad_e grad[1] = grad_fwhm_L return grad
[docs]def deriv_voigt(e: Union[float, np.ndarray], fwhm_G: float, fwhm_L: float) -> np.ndarray: ''' deriv_voigt: derivative of voigt profile with respect to (e, fwhm_G, fwhm_L) Args: e: energy fwhm_G: full width at half maximum of gaussian part :math:(2\\sqrt{2\\log(2)}\\sigma) fwhm_L: full width at half maximum of lorenzian part :math:(2\\gamma) Returns: first derivative of voigt profile Note: * 1st column: df/de * 2nd column: df/d(fwhm_G) * 3rd column: df/d(fwhm_L) if `fwhm_G` is (<1e-8) then, * 1st column: dl/de * 2nd column: 0 * 3rd column: dL/d(fwhm_L) L means normalized lorenzian shape with full width at half maximum parameter: `fwhm_L` if `fwhm_L` is (<1e-8) it returns * 1st column: dg/de * 2nd column: dg/d(fwhm_G) * 3rd column: 0 g means normalized gaussian shape with full width at half maximum parameter: `fwhm_G` ''' if fwhm_G < 1e-8: tmp = fwhm_L/2/np.pi/(e**2+fwhm_L**2/4)**2 if isinstance(e, np.ndarray): grad = np.empty((e.size, 3)) grad[:, 0] = - 2*e*tmp grad[:, 1] = 0 grad[:, 2] = (1/np.pi/(e**2+fwhm_L**2/4)-fwhm_L*tmp)/2 else: grad = np.empty(3) grad[0] = -2*e*tmp grad[1] = 0 grad[2] = (1/np.pi/(e**2+fwhm_L**2/4)-fwhm_L*tmp)/2 return grad sigma = fwhm_G/(2*np.sqrt(2*np.log(2))) if fwhm_L < 1e-8: tmp = np.exp(-(e/sigma)**2/2)/(sigma*np.sqrt(2*np.pi)) if isinstance(e, np.ndarray): grad = np.empty((e.size, 3)) grad[:, 0] = -e/sigma**2*tmp grad[:, 1] = ((e/sigma)**2-1)/fwhm_G*tmp grad[:, 2] = 0 else: grad = np.empty(3) grad[0] = -e/sigma**2*tmp grad[1] = ((e/sigma)**2-1)/sigma*tmp grad[2] = 0 return grad z = (e+complex(0,fwhm_L/2))/(sigma*np.sqrt(2)) f = wofz(z)/(sigma*np.sqrt(2*np.pi)) f_z = (complex(0, 2/np.sqrt(np.pi))-2*z*wofz(z))/(sigma*np.sqrt(2*np.pi)) if isinstance(e, np.ndarray): grad = np.empty((e.size, 3)) grad[:, 0] = f_z.real/(sigma*np.sqrt(2)) grad[:, 1] = (-f/sigma-z/sigma*f_z).real/(2*np.sqrt(2*np.log(2))) grad[:, 2] = -f_z.imag/(2*np.sqrt(2)*sigma) else: grad = np.empty(3) grad[0] = f_z.real/(sigma*np.sqrt(2)) grad[1] = (-f/sigma-z/sigma*f_z).real/(2*np.sqrt(2*np.log(2))) grad[2] = -f_z.imag/(2*np.sqrt(2)*sigma) return grad
[docs]def deriv_voigt_thy(e: np.ndarray, thy_peak: np.ndarray, fwhm_G: float, fwhm_L: float, peak_factor: Union[float, np.ndarray], policy: Optional[str] = 'shift') -> np.ndarray: ''' Calculates derivative of normalized voigt broadened theoretically calculated lineshape spectrum Args: e: energy thy_peak: theoretical calculated peak position and intensity fwhm_G: full width at half maximum of gaussian shape fwhm_L: full width at half maximum of lorenzian shape peak_factor: Peak factor, its behavior depends on policy. policy ({'shift', 'scale', 'both'}): Policy to match discrepency between experimental data and theoretical spectrum. * 'shift' : Default option, shift peak position by peak_factor * 'scale' : scale peak position by peak_factor * 'both' : both shift and scale peak postition. `peak_factor` should be equal to the pair of `shift_factor` and `scale_factor`. Returns: derivative of normalized voigt broadened theoritical lineshape spectrum Note: * 1st column: df/d(fwhm_G) * 2nd column: df/d(fwhm_L) if policy is `shift` or `scale`, * 3rd column: df/d(peak_factor) if policy is `both` * 3rd column: df/d(peak_factor[0]), peak_factor[0]: shift factor * 4th column: df/d(peak_factor[1]), peak_factor[1]: scale factor ''' deriv_voigt_tensor = np.empty((e.size, 3, thy_peak.shape[0])) peak_copy = np.copy(thy_peak[:, 0]) if policy == 'shift': peak_copy = peak_copy + peak_factor elif policy == 'scale': peak_copy = peak_factor*peak_copy else: peak_copy = peak_factor[1]*peak_copy + peak_factor[0] for i in range(peak_copy.size): deriv_voigt_tensor[:, :, i] = deriv_voigt(e-peak_copy[i], fwhm_G, fwhm_L) if policy in ['shift', 'scale']: grad = np.empty((e.size, 3)) else: grad = np.empty((e.size, 4)) grad[:, 0] = deriv_voigt_tensor[:, 1, :] @ thy_peak[:, 1] grad[:, 1] = deriv_voigt_tensor[:, 2, :] @ thy_peak[:, 1] if policy == 'shift': grad[:, 2] = -deriv_voigt_tensor[:, 0, :] @ thy_peak[:, 1] elif policy == 'scale': grad[:, 2] = -deriv_voigt_tensor[:, 0, :] @ \ (thy_peak[:, 0]*thy_peak[:, 1]) else: grad[:, 2] = -deriv_voigt_tensor[:, 0, :] @ thy_peak[:, 1] grad[:, 3] = -deriv_voigt_tensor[:, 0, :] @ \ (thy_peak[:, 0]*thy_peak[:, 1]) return grad