Source code for sunkit_spex.legacy.photon_power_law

import functools

import numpy as np

import astropy.units as u

PHOTON_RATE_UNIT = u.ph / u.keV / u.cm**2 / u.s


[docs] @u.quantity_input def integrate_power_law( energy: u.keV, norm_energy: u.keV, norm: PHOTON_RATE_UNIT, index: u.one ) -> PHOTON_RATE_UNIT * u.keV: r"""Evaluate the antiderivative of a power law at a given energy or vector of energies. The power law antiderivative evaluated by this function is assumed to take the following form, :math:`f(E) = N \left( \frac{E}{E_0} \right)^{- \gamma}`, where :math:`E` is the energy, :math:`N` is the normalization, :math:`E_0` is the normalization energy, and :math:`\gamma` is the power law index. The value of :math:`\gamma` is assumed to be positive, but the functional form includes a negative sign. The special case of :math:`\gamma = 1` is handled. Parameters ---------- energy : `astropy.units.Quantity` Energy (or vector of energies) at which to evaluate the power law antiderivative. norm_energy : `astropy.units.Quantity` Energy used for the normalization of the power law argument, i.e. :math:`E_0`. norm : `astropy.units.Quantity` Normalization of the power law integral, i.e. :math:`N`, in units convertible to ph / (cm2 . keV . s). index : `astropy.units.Quantity` The power law index, i.e. :math:`\gamma`. Returns ------- `astropy.units.Quantity` Analytical antiderivative of a power law evaluated at the given energies. """ prefactor = norm * norm_energy arg = (energy / norm_energy).to(u.one) if index == 1: return prefactor * np.log(arg) return (prefactor / (1 - index)) * arg ** (1 - index)
[docs] @u.quantity_input def compute_broken_power_law( energy_edges: u.keV, norm_energy: u.keV, norm_flux: PHOTON_RATE_UNIT, break_energy: u.keV, lower_index: u.one, upper_index: u.one, ) -> PHOTON_RATE_UNIT: r"""Analytically evaluate a photon-space broken power law and bin the flux. The broken power law is assumed to take the following form, .. math:: f(E \le E_b) = N_1 \left( \frac{E}{E_0} \right)^{-\gamma_1} \\ f(E > E_b) = N_2 \left( \frac{E}{E_0} \right)^{-\gamma_2} where :math:`E` is the energy, :math:`N_1` and :math:`N_2` are the normalizations below and above the break, :math:`E_0` is the normalization energy, :math:`E_b` is the break energy, and :math:`\gamma_1` and :math:`\gamma_2` are the upper and lower power law indices. Only one normalization flux and energy are given. Continuity is enforced at the break energy so that the normalization is correct at the chosen energy. The values of :math:`\gamma_1` and :math:`\gamma_2` are assumed to be positive, but the functional form includes negative signs. Parameters ---------- energy_edges : `astropy.units.Quantity` 1D array of energy edges defining the energy bins. norm_energy : `astropy.units.Quantity` Energy at which the normalization is applied, i.e. :math:`E_0`. norm_flux : `astropy.units.Quantity` Normalization flux for the photon power law. The `norm_flux` corresponds to either :math:`N_1` or :math:`N_2` depending on if the energy is below or above the break. break_energy : `astropy.units.Quantity` Break energy of the broken power law. The energy bin containing the break energy will be a combination of the lower and upper power laws. lower_index : `astropy.units.Quantity` Lower power law index. upper_index : `astropy.units.Quantity` Upper power law index. Returns ------- `astropy.units.Quantity` Photon broken power law, where the flux in each energy bin is equal to the broken power law analytically averaged over each bin. """ if energy_edges.size <= 1: raise ValueError("Need at least two energy edges.") if norm_flux == 0: return np.zeros(energy_edges.size - 1) << PHOTON_RATE_UNIT up_norm, low_norm = _compute_broken_power_law_normalizations( norm_flux, norm_energy, break_energy, lower_index, upper_index ) condition = energy_edges < break_energy lower = energy_edges[condition] upper = energy_edges[~condition] up_integ = functools.partial(integrate_power_law, norm_energy=norm_energy, norm=up_norm, index=upper_index) low_integ = functools.partial(integrate_power_law, norm_energy=norm_energy, norm=low_norm, index=lower_index) lower_portion = low_integ(energy=lower[1:]) - low_integ(energy=lower[:-1]) upper_portion = up_integ(energy=upper[1:]) - up_integ(energy=upper[:-1]) twixt_portion = [] # bin between the portions is comprised of both power laws if lower.size > 0 and upper.size > 0: twixt_portion = np.diff(low_integ(energy=u.Quantity([lower[-1], break_energy]))) twixt_portion += np.diff(up_integ(energy=u.Quantity([break_energy, upper[0]]))) ret = np.concatenate((lower_portion, twixt_portion, upper_portion)) if ret.size != (energy_edges.size - 1): raise ValueError("Bin or edge size mismatch. Bug?") # go back to units of cm2/sec/keV return ret / np.diff(energy_edges)
[docs] @u.quantity_input def compute_power_law( energy_edges: u.keV, norm_energy: u.keV, norm_flux: PHOTON_RATE_UNIT, index: u.one ) -> PHOTON_RATE_UNIT: r"""Single power law, defined by setting the break energy to -inf and the lower index to nan. Parameters ---------- energy_edges : `astropy.units.Quantity` 1D array of energy edges defining the energy bins. norm_energy : `astropy.units.Quantity` Energy at which the normalization is applied, i.e. :math:`E_0`. norm_flux : `astropy.units.Quantity` Normalization flux for the photon power law. See index : `astropy.units.Quantity` Power law index. Returns ------- `astropy.units.Quantity` Photon power law, where the flux in each energy bin is equal to the power law analytically averaged over each bin. """ return compute_broken_power_law( energy_edges=energy_edges, norm_energy=norm_energy, norm_flux=norm_flux, break_energy=-np.inf << u.keV, lower_index=np.nan, upper_index=index, )
@u.quantity_input def _compute_broken_power_law_normalizations( norm_flux: PHOTON_RATE_UNIT, norm_energy: u.keV, break_energy: u.keV, lower_index: u.one, upper_index: u.one ) -> PHOTON_RATE_UNIT: """(internal) give the correct upper and lower power law normalizations given the desired flux and locations of break & norm energies """ energy_arg = (break_energy / norm_energy).to(u.one) if norm_energy < break_energy: low_norm = norm_flux up_norm = norm_flux * energy_arg ** (upper_index - lower_index) else: up_norm = norm_flux low_norm = norm_flux * energy_arg ** (lower_index - upper_index) return u.Quantity((up_norm, low_norm))