Source code for xarpes.distributions

# Copyright (C) 2024 xARPES Developers
# This program is free software under the terms of the GNU GPLv3 license.

"""The distributions used throughout the code."""

[docs] class distribution: r"""Parent class for distributions. The class cannot be used on its own, but is used to instantiate unique and non-unique distributions. Parameters ---------- name : str Non-unique name for instances, not to be modified after instantiation. """ def __init__(self, name): self._name = name @property def name(self): r"""Returns the name of the class instance. Returns ------- name : str Non-unique name for instances, not to be modified after instantiation. """ return self._name
[docs] class unique_distribution(distribution): r"""Parent class for unique distributions, to be used one at a time, e.g., during the background of an MDC fit or the Fermi-Dirac distribution. Parameters ---------- label : str Unique label for instances, identical to the name for unique distributions. Not to be modified after instantiation. """ def __init__(self, name): super().__init__(name) self._label = name @property def label(self): r"""Returns the unique class label. Returns ------- label : str Unique label for instances, identical to the name for unique distributions. Not to be modified after instantiation. """ return self._label
[docs] class constant(unique_distribution): r"""Child class for constant distributions, used e.g., during MDC fitting. The constant class is unique, only one instance should be used per task. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. """ def __init__(self, offset): super().__init__(name='constant') self._offset = offset @property def offset(self): r"""Returns the offset of the constant distribution. Returns ------- offset : float The value of the distribution for the abscissa equal to 0. """ return self._offset @offset.setter def set_offset(self, x): r"""Sets the offset of the constant distribution. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. """ self._offset = x
class linear(unique_distribution): r"""Child cass for for linear distributions, used e.g., during MDC fitting. The constant class is unique, only one instance should be used per task. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. slope : float The linear slope of the distribution w.r.t. the abscissa. """ def __init__(self, slope, offset): super().__init__(name='linear') self._offset = offset self._slope = slope @property def offset(self): r"""Returns the offset of the linear distribution. Returns ------- offset : float The value of the distribution for the abscissa equal to 0. """ return self._offset @offset.setter def set_offset(self, x): r"""Sets the offset of the linear distribution. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. """ self._offset = x @property def slope(self): r"""Returns the slope of the linear distribution. Returns ------- slope : float The linear slope of the distribution w.r.t. the abscissa. """ return self._slope @slope.setter def set_slope(self, x): r"""Sets the slope of the linear distribution. Parameters ---------- slope : float The linear slope of the distribution w.r.t. the abscissa. """ self._slope = x
[docs] class linear(unique_distribution): r"""Child cass for for linear distributions, used e.g., during MDC fitting. The constant class is unique, only one instance should be used per task. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. slope : float The linear slope of the distribution w.r.t. the abscissa. """ def __init__(self, slope, offset): super().__init__(name='linear') self._offset = offset self._slope = slope @property def offset(self): r"""Returns the offset of the linear distribution. Returns ------- offset : float The value of the distribution for the abscissa equal to 0. """ return self._offset @offset.setter def set_offset(self, x): r"""Sets the offset of the linear distribution. Parameters ---------- offset : float The value of the distribution for the abscissa equal to 0. """ self._offset = x @property def slope(self): r"""Returns the slope of the linear distribution. Returns ------- slope : float The linear slope of the distribution w.r.t. the abscissa. """ return self._slope @slope.setter def set_slope(self, x): r"""Sets the slope of the linear distribution. Parameters ---------- slope : float The linear slope of the distribution w.r.t. the abscissa. """ self._slope = x
[docs] class fermi_dirac(unique_distribution): r"""Child class for Fermi-Dirac (FD) distributions, used e.g., during Fermi edge fitting. The FD class is unique, only one instance should be used per task. The Fermi-Dirac distribution is described by the following formula: .. math:: \frac{A}{\rm{e}^{\beta(E_{\rm{kin}}-(h\nu-\Phi))}+1} + B with :math:`A` as :attr:`integrated_weight`, :math:`B` as :attr:`background`, :math:`h\nu-\Phi` as :attr:`hnuminphi`, and :math:`\beta=1/(k_{\rm{B}}T)` with :math:`T` as :attr:`temperature`. Parameters ---------- temperature : float Temperature of the sample [K] hnuminphi : float Kinetic energy minus the work function [eV] background : float Background spectral weight [counts] integrated_weight : float Integrated weight on top of the background [counts] """ def __init__(self, temperature, hnuminphi, background=0, integrated_weight=1, name='fermi_dirac'): super().__init__(name) self.temperature = temperature self.hnuminphi = hnuminphi self.background = background self.integrated_weight = integrated_weight @property def temperature(self): r"""Returns the temperature of the sample. Returns ------- temperature : float Temperature of the sample [K] """ return self._temperature @temperature.setter def set_temperature(self, x): r"""Sets the temperature of the FD distribution. Parameters ---------- temperature : float Temperature of the sample [K] """ self._temperature = x @property def hnuminphi(self): r"""Returns the photon energy minus the work function of the FD distribution. Returns ------- hnuminphi: float Kinetic energy minus the work function [eV] """ return self._hnuminphi @hnuminphi.setter def set_hnuminphi(self, x): r"""Sets the photon energy minus the work function of the FD distribution. Parameters ---------- hnuminphi : float Kinetic energy minus the work function [eV] """ self._hnuminphi = x @property def background(self): r"""Returns the background intensity of the FD distribution. Returns ------- background : float Background spectral weight [counts] """ return self._background @background.setter def set_background(self, x): r"""Sets the background intensity of the FD distribution. Parameters ---------- background : float Background spectral weight [counts] """ self._background = x @property def integrated_weight(self): r"""Returns the integrated weight of the FD distribution. Returns ------- integrated_weight: float Integrated weight on top of the background [counts] """ return self._integrated_weight @integrated_weight.setter def set_integrated_weight(self, x): r"""Sets the integrated weight of the FD distribution. Parameters ---------- integrated_weight : float Integrated weight on top of the background [counts] """ self._integrated_weight = x def __call__(self, energy_range, hnuminphi, background, integrated_weight, energy_resolution): """Call method to directly evaluate a FD distribution without having to instantiate a class instance. Parameters ---------- energy_range : ndarray 1D array on which to evaluate the FD distribution [eV] hnuminphi : float Kinetic energy minus the work function [eV] background : float Background spectral weight [counts] integrated_weight : float Integrated weight on top of the background [counts] energy_resolution : float Energy resolution of the detector for the convolution [eV] Returns ------- evalf : ndarray 1D array of the energy-convolved FD distribution [counts] """ from scipy.ndimage import gaussian_filter import numpy as np sigma_extend = 5 # Extend data range by "5 sigma" # Conversion from FWHM to standard deviation [-] fwhm_to_std = np.sqrt(8 * np.log(2)) k_B = 8.617e-5 # Boltzmann constant [eV/K] k_BT = self.temperature * k_B step_size = np.abs(energy_range[1] - energy_range[0]) estep = energy_resolution / (step_size * fwhm_to_std) enumb = int(sigma_extend * estep) extend = np.linspace(energy_range[0] - enumb * step_size, energy_range[-1] + enumb * step_size, len(energy_range) + 2 * enumb) result = (integrated_weight / (1 + np.exp((extend - hnuminphi) / k_BT)) + background) evalf = gaussian_filter(result, sigma=estep)[enumb:-enumb] return evalf
[docs] def evaluate(self, energy_range): r"""Evaluates the FD distribution for a given class instance. No energy convolution is performed with evaluate. Parameters ---------- energy_range : ndarray 1D array on which to evaluate the FD distribution [eV] Returns ------- evalf : ndarray 1D array of the evaluated FD distribution [counts] """ import numpy as np k_B = 8.617e-5 # Boltzmann constant [eV/K] k_BT = self.temperature * k_B evalf = (self.integrated_weight / (1 + np.exp((energy_range - self.hnuminphi) / k_BT)) + self.background) return evalf
[docs] def convolve(self, energy_range, energy_resolution): r"""Evaluates the FD distribution for a given class instance and performs the energy convolution with the given resolution. The convolution is performed with an expanded abscissa range of 5 times the standard deviation. Parameters ---------- energy_range : ndarray 1D array on which to evaluate and convolve FD distribution [eV] energy_resolution : float Energy resolution of the detector for the convolution [eV] Returns ------- evalf : ndarray 1D array of the energy-convolved FD distribution [counts] """ import numpy as np from scipy.ndimage import gaussian_filter sigma_extend = 5 # Extend data range by "5 sigma" # Conversion from FWHM to standard deviation [-] fwhm_to_std = np.sqrt(8 * np.log(2)) step_size = np.abs(energy_range[1] - energy_range[0]) estep = energy_resolution / (step_size * fwhm_to_std) enumb = int(sigma_extend * estep) extend = np.linspace(energy_range[0] - enumb * step_size, energy_range[-1] + enumb * step_size, len(energy_range) + 2 * enumb) evalf = gaussian_filter(self.evaluate(extend), sigma=estep)[enumb:-enumb] return evalf