Source code for NuRadioMC.SignalProp.propagation_base_class

from __future__ import absolute_import, division, print_function
from NuRadioReco.utilities import units
from NuRadioMC.SignalProp.propagation import solution_types, solution_types_revert
import numpy as np
import logging
logging.basicConfig()

"""
Structure of a ray-tracing module. For documentation and development purposes.
"""

[docs]class ray_tracing_base: """ base class of ray tracer. All ray tracing modules need to prodide the following functions """ def __init__(self, medium, attenuation_model=None, log_level=logging.WARNING, n_frequencies_integration=None, n_reflections=None, config=None, detector=None, ray_tracing_2D_kwards={}): """ class initilization Parameters ---------- medium: medium class class describing the index-of-refraction profile attenuation_model: string signal attenuation model (so far only "SP1" is implemented) log_level: logging object specify the log level of the ray tracing class * logging.ERROR * logging.WARNING * logging.INFO * logging.DEBUG default is WARNING n_frequencies_integration: int the number of frequencies for which the frequency dependent attenuation length is being calculated. The attenuation length for all other frequencies is obtained via linear interpolation. n_reflections: int (default 0) in case of a medium with a reflective layer at the bottom, how many reflections should be considered config: nested dictionary loaded yaml config file detector: detector object ray_tracing_2D_kwards: dict Additional arguments which are passed to ray_tracing_2D """ self.__logger = logging.getLogger('ray_tracing_base') self.__logger.setLevel(log_level) self._medium = medium self._attenuation_model = attenuation_model self._n_frequencies_integration = n_frequencies_integration if(n_reflections): if(not hasattr(self._medium, "reflection") or self._medium.reflection is None): self.__logger.warning("ray paths with bottom reflections requested medium does not have any reflective layer, setting number of reflections to zero.") n_reflections = 0 self._n_reflections = n_reflections self._config = config self._detector = detector self._max_detector_frequency = None if self._detector is not None: for station_id in self._detector.get_station_ids(): sampling_frequency = self._detector.get_sampling_frequency(station_id, 0) if self._max_detector_frequency is None or sampling_frequency * .5 > self._max_detector_frequency: self._max_detector_frequency = sampling_frequency * .5 self._X1 = None self._X2 = None self._results = None
[docs] def reset_solutions(self): self._X1 = None self._X2 = None self._results = None
[docs] def set_start_and_end_point(self, x1, x2): """ Set the start and end points between which raytracing solutions shall be found It is recommended to also reset the solutions from any previous raytracing to avoid confusing them with the current solution Parameters ---------- x1: np.array of shape (3,), default unit start point of the ray x2: np.array of shape (3,), default unit stop point of the ray """ self.reset_solutions() self._X1 = np.array(x1, dtype =float) self._X2 = np.array(x2, dtype = float) if (self._n_reflections): if (self._X1[2] < self._medium.reflection or self._X2[2] < self._medium.reflection): self.__logger.error("start or stop point is below the reflective bottom layer at {:.1f}m".format( self._medium.reflection / units.m)) raise AttributeError("start or stop point is below the reflective bottom layer at {:.1f}m".format( self._medium.reflection / units.m))
[docs] def use_optional_function(self, function_name, *args, **kwargs): """ Use optional function which may be different for each ray tracer. If the name of the function is not present for the ray tracer this function does nothing. Parameters ---------- function_name: string name of the function to use *args: type of the argument required by function all the neseccary arguments for the function separated by a comma **kwargs: type of keyword argument of function all all the neseccary keyword arguments for the function in the form of key=argument and separated by a comma Examples -------- .. code-block:: use_optional_function('set_shower_axis',np.array([0,0,1])) use_optional_function('set_iterative_sphere_sizes',sphere_sizes=np.aray([3,1,.5])) """ if not hasattr(self,function_name): pass else: getattr(self,function_name)(*args,**kwargs)
[docs] def find_solutions(self): """ find all solutions between x1 and x2 """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def has_solution(self): """ checks if ray tracing solution exists """ return len(self._results) > 0
[docs] def get_number_of_solutions(self): """ returns the number of solutions """ return len(self._results)
[docs] def get_results(self): """ """ return self._results
[docs] def get_solution_type(self, iS): """ returns the type of the solution Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero Returns ------- solution_type: int integer corresponding to the types in the dictionary solution_types """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_path(self, iS, n_points=1000): """ helper function that returns the 3D ray tracing path of solution iS Parameters ---------- iS: int ray tracing solution n_points: int number of points of path """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_launch_vector(self, iS): """ calculates the launch vector (in 3D) of solution iS Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero Returns ------- launch_vector: 3dim np.array the launch vector """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_receive_vector(self, iS): """ calculates the receive vector (in 3D) of solution iS Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero Returns ------- receive_vector: 3dim np.array the receive vector """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_reflection_angle(self, iS): """ calculates the angle of reflection at the surface (in case of a reflected ray) Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero Returns ------- reflection_angle: float or None the reflection angle (for reflected rays) or None for direct and refracted rays """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_path_length(self, iS, analytic=True): """ calculates the path length of solution iS Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero analytic: bool If True the analytic solution is used. If False, a numerical integration is used. (default: True) Returns ------- distance: float distance from x1 to x2 along the ray path """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_travel_time(self, iS, analytic=True): """ calculates the travel time of solution iS Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero analytic: bool If True the analytic solution is used. If False, a numerical integration is used. (default: True) Returns ------- time: float travel time """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_attenuation(self, iS, frequency, max_detector_freq=None): """ calculates the signal attenuation due to attenuation in the medium (ice) Parameters ---------- iS: int choose for which solution to compute the launch vector, counting starts at zero frequency: array of floats the frequencies for which the attenuation is calculated max_detector_freq: float or None the maximum frequency of the final detector sampling (the simulation is internally run with a higher sampling rate, but the relevant part of the attenuation length calculation is the frequency interval visible by the detector, hence a finer calculation is more important) Returns ------- attenuation: array of floats the fraction of the signal that reaches the observer (only ice attenuation, the 1/R signal falloff not considered here) """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def apply_propagation_effects(self, efield, i_solution): """ Apply propagation effects to the electric field Note that the 1/r weakening of the electric field is already accounted for in the signal generation Parameters ---------- efield: ElectricField object The electric field that the effects should be applied to i_solution: int Index of the raytracing solution the propagation effects should be based on Returns ------- efield: ElectricField object The modified ElectricField object """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_output_parameters(self): """ Returns a list with information about parameters to include in the output data structure that are specific to this raytracer ! be sure that the first entry is specific to your raytracer ! Returns ------- list with entries of form [{'name': str, 'ndim': int}] ! be sure that the first entry is specific to your raytracer ! 'name': Name of the new parameter to include in the data structure 'ndim': Dimension of the data structure for the parameter """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_raytracing_output(self, i_solution): """ Write parameters that are specific to this raytracer into the output data. Parameters ---------- i_solution: int The index of the raytracing solution Returns ------- dictionary with the keys matching the parameter names specified in get_output_parameters and the values being the results from the raytracing """ self.__logger.error('function not defined') raise NotImplementedError
[docs] def get_number_of_raytracing_solutions(self): """ Function that returns the maximum number of raytracing solutions that can exist between each given pair of start and end points """ return 2 + 4 * self._n_reflections # number of possible ray-tracing solutions
[docs] def get_config(self): """ Function that returns the configuration currently used by the raytracer """ return self._config
[docs] def set_config(self, config): """ Function to change the configuration file used by the raytracer Parameters ---------- config: dict The new configuration settings """ self._config = config