Source code for tardis.spectrum.formal_integral.formal_integral

from tardis.opacities.opacity_state import opacity_state_initialize
import numpy as np
from astropy import units as u
from scipy.interpolate import interp1d
import warnings
from tardis.spectrum.formal_integral.formal_integral_cuda import CudaFormalIntegrator
from tardis.spectrum.formal_integral.formal_integral_numba import NumbaFormalIntegrator, calculate_p_values, trapezoid_integration
from tardis.spectrum.formal_integral.base import check, make_source_function
from tardis.spectrum.spectrum import TARDISSpectrum
from tardis.transport.montecarlo.configuration import montecarlo_globals

[docs] class FormalIntegrator: """ Class containing the formal integrator. If there is a NVIDIA CUDA GPU available, the formal integral will automatically run on it. If multiple GPUs are available, it will choose the first one that it sees. You can read more about selecting different GPUs on Numba's CUDA documentation. Parameters ---------- model : tardis.model.SimulationState plasma : tardis.plasma.BasePlasma transport : tardis.transport.montecarlo.MontecarloTransport points : int64 """ def __init__( self, simulation_state, plasma, transport, opacity_state=None, macro_atom_state=None, points=1000, ): self.simulation_state = simulation_state self.transport = transport self.points = points if transport: self.montecarlo_configuration = ( self.transport.montecarlo_configuration ) if plasma and opacity_state and macro_atom_state: self.opacity_state = opacity_state.to_numba( macro_atom_state, transport.line_interaction_type, ) self.atomic_data = plasma.atomic_data self.plasma = plasma self.levels_index = plasma.levels elif plasma: self.opacity_state = opacity_state_initialize( plasma, transport.line_interaction_type, self.montecarlo_configuration.DISABLE_LINE_SCATTERING, ) self.atomic_data = plasma.atomic_data self.plasma = plasma self.levels_index = plasma.levels else: self.opacity_state = None
[docs] def generate_numba_objects(self): """instantiate the numba interface objects needed for computing the formal integral """ from tardis.model.geometry.radial1d import NumbaRadial1DGeometry self.numba_radial_1d_geometry = NumbaRadial1DGeometry( self.transport.r_inner_i, self.transport.r_outer_i, self.transport.r_inner_i / self.simulation_state.time_explosion.to("s").value, self.transport.r_outer_i / self.simulation_state.time_explosion.to("s").value, ) if self.opacity_state is None: self.opacity_state = opacity_state_initialize( self.plasma, self.transport.line_interaction_type, self.montecarlo_configuration.DISABLE_LINE_SCATTERING, ) if self.transport.use_gpu: self.integrator = CudaFormalIntegrator( self.numba_radial_1d_geometry, self.simulation_state.time_explosion.cgs.value, self.opacity_state, self.points, ) else: self.integrator = NumbaFormalIntegrator( self.numba_radial_1d_geometry, self.simulation_state.time_explosion.cgs.value, self.opacity_state, self.points, )
[docs] def calculate_spectrum( self, frequency, points=None, interpolate_shells=0, raises=True ): # Very crude implementation # The c extension needs bin centers (or something similar) # while TARDISSpectrum needs bin edges check(self.simulation_state, self.plasma, self.transport, raises=raises) N = points or self.points if interpolate_shells == 0: # Default Value interpolate_shells = max(2 * self.simulation_state.no_of_shells, 80) warnings.warn( "The number of interpolate_shells was not " f"specified. The value was set to {interpolate_shells}." ) self.interpolate_shells = interpolate_shells frequency = frequency.to("Hz", u.spectral()) luminosity = u.Quantity(self.formal_integral(frequency, N), "erg") * ( frequency[1] - frequency[0] ) # Ugly hack to convert to 'bin edges' frequency = u.Quantity( np.concatenate( [ frequency.value, [frequency.value[-1] + np.diff(frequency.value)[-1]], ] ), frequency.unit, ) return TARDISSpectrum(frequency, luminosity)
[docs] def formal_integral(self, nu, N): """Do the formal integral with the numba routines """ # TODO: get rid of storage later on res = make_source_function(self.simulation_state, self.opacity_state, self.transport, self.plasma, self.interpolate_shells) att_S_ul = res[0].flatten(order="F") Jred_lu = res[1].flatten(order="F") Jblue_lu = res[2].flatten(order="F") self.generate_numba_objects() L, I_nu_p = self.integrator.formal_integral( self.simulation_state.t_inner, nu, nu.shape[0], att_S_ul, Jred_lu, Jblue_lu, self.transport.tau_sobolevs_integ, self.transport.electron_densities_integ, N, ) R_max = self.transport.r_outer_i[-1] ps = calculate_p_values(R_max, N)[None, :] I_nu_p[:, 1:] /= ps[:, 1:] self.transport.I_nu_p = I_nu_p self.transport.p_rays = ps I_nu = self.transport.I_nu_p * ps L_test = np.array( [ 8 * np.pi * np.pi * trapezoid_integration((I_nu)[i, :], R_max / N) for i in range(nu.shape[0]) ] ) error = np.max(np.abs((L_test - L) / L)) assert ( error < 1e-7 ), f"Incorrect I_nu_p values, max relative difference:{error}" return np.array(L, np.float64)