From 76a2a37369a41b2cfcf64e509ef9baf0bc4cd2be Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Thu, 6 Apr 2023 16:26:42 +0200 Subject: [PATCH 01/54] enable channel/trigger-specific readout windows --- NuRadioReco/modules/triggerTimeAdjuster.py | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index 01f893e5d..9c8567ce2 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -29,9 +29,16 @@ def begin(self, trigger_name=None, pre_trigger_time=55. * units.ns): If trigger_name is None, the trigger with the smalles trigger_time will be used. If a name is give, corresponding trigger module must be run beforehand. If the trigger does not exist or did not trigger, this module will do nothing - pre_trigger_time: float - Amount of time that should be stored in the channel trace before the trigger. If the channel trace is long - enough, it will be cut accordingly. Otherwise, it will be rolled. + pre_trigger_time: float or dict + Amount of time that should be stored in the channel trace before the trigger. + If the channel trace is long enough, it will be cut accordingly. + Otherwise, it will be rolled. + + If given as a float, the same ``pre_trigger_time`` will be used for all channels. + If a dict, the keys should be ``channel_id``, and the values the ``pre_trigger_time`` + to use for each channel. Alternatively, the keys should be the ``trigger_name``, + and the values either a float or a dictionary with (``channel_id``, ``pre_trigger_time``) + pairs. """ self.__trigger_name = trigger_name self.__pre_trigger_time = pre_trigger_time @@ -69,7 +76,22 @@ def run(self, event, station, detector): sampling_rate = channel.get_sampling_rate() trigger_time_sample = int(np.round(trigger_time_channel * sampling_rate)) # logger.debug(f"channel {channel.get_id()}: trace_start_time = {channel.get_trace_start_time():.1f}ns, trigger time channel {trigger_time_channel/units.ns:.1f}ns, trigger time sample = {trigger_time_sample}") - samples_before_trigger = int(self.__pre_trigger_time * sampling_rate) + pre_trigger_time = self.__pre_trigger_time + channel_id = channel.get_id() + trigger_name = trigger.get_name() + while isinstance(pre_trigger_time, dict): + if trigger_name in pre_trigger_time.keys(): # keys are different triggers + pre_trigger_time = pre_trigger_time[trigger_name] + elif channel_id in pre_trigger_time: # keys are channel_ids + pre_trigger_time = pre_trigger_time[channel_id] + else: + logger.error( + 'pre_trigger_time was specified as a dictionary, ' + f'but the neither the trigger_name {trigger_name} ' + f'nor the channel id {channel_id} are present as keys' + ) + raise KeyError + samples_before_trigger = int(pre_trigger_time * sampling_rate) rel_station_time_samples = 0 cut_samples_beginning = 0 if(samples_before_trigger < trigger_time_sample): From 920c01ec5543eae3596ed5a9466223f4783b511d Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Fri, 4 Aug 2023 09:17:39 +0200 Subject: [PATCH 02/54] correct number of samples in triggerTimeAdjuster --- NuRadioReco/modules/triggerTimeAdjuster.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index 9c8567ce2..002b30eb5 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -67,7 +67,11 @@ def run(self, event, station, detector): trace = channel.get_trace() trace_length = len(trace) - number_of_samples = int(detector.get_number_of_samples(station.get_id(), channel.get_id()) * channel.get_sampling_rate() / detector.get_sampling_frequency(station.get_id(), channel.get_id())) + number_of_samples = int( + 2 * np.ceil( # this should ensure that 1) the number of samples is even and 2) resampling to the detector sampling rate results in the correct number of samples (note that 2) can only be guaranteed if the detector sampling rate is lower than the current sampling rate) + detector.get_number_of_samples(station.get_id(), channel.get_id()) / 2 + * channel.get_sampling_rate() / detector.get_sampling_frequency(station.get_id(), channel.get_id()) + )) if number_of_samples > trace.shape[0]: logger.error("Input has fewer samples than desired output. Channels has only {} samples but {} samples are requested.".format( trace.shape[0], number_of_samples)) From 949e9b37855281021721af61904ec64ae48fb401 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 4 Aug 2023 12:25:37 +0000 Subject: [PATCH 03/54] add option to trace from ice into air. --- NuRadioMC/SignalProp/analyticraytracing.py | 152 +++++++++++++-------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index bac2c061b..4ba7bccb3 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -40,6 +40,8 @@ print("check NuRadioMC/NuRadioMC/SignalProp/CPPAnalyticRayTracing for manual compilation") cpp_available = False +cpp_available = False + """ analytic ray tracing solution """ @@ -132,11 +134,10 @@ def __init__(self, medium, attenuation_model="SP1", self.__logger.setLevel(log_level) self.__n_frequencies_integration = n_frequencies_integration self.__use_optimized_start_values = use_optimized_start_values - + self._use_optimized_calculation = self.attenuation_model in speedup_attenuation_models if overwrite_speedup is not None: self._use_optimized_calculation = overwrite_speedup - def n(self, z): """ @@ -590,19 +591,18 @@ def __get_frequencies_for_attenuation(self, frequency, max_detector_freq): self.__logger.debug(f"calculating attenuation for frequencies {freqs}") return freqs - - def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, + def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, reflection=0, reflection_case=1): - + attenuation = np.ones_like(frequency) output = f"calculating attenuation for n_ref = {int(reflection):d}: " - for iS, segment in enumerate(self.get_path_segments(x1, x2, C_0, reflection, + for iS, segment in enumerate(self.get_path_segments(x1, x2, C_0, reflection, reflection_case)): - - # we can only integrate upward going rays, so if the ray starts downwardgoing, + + # we can only integrate upward going rays, so if the ray starts downwardgoing, # we need to mirror - if iS == 0 and reflection_case == 2: + if iS == 0 and reflection_case == 2: x11, x1, x22, x2, C_0, C_1 = segment x1t = copy.copy(x11) x2t = copy.copy(x2) @@ -612,20 +612,20 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, x1 = x1t else: x11, x1, x22, x2, C_0, C_1 = segment - + if cpp_available: mask = frequency > 0 freqs = self.__get_frequencies_for_attenuation(frequency, max_detector_freq) tmp = np.zeros_like(freqs) for i, f in enumerate(freqs): tmp[i] = wrapper.get_attenuation_along_path( - x1, x2, C_0, f, self.medium.n_ice, self.medium.delta_n, + x1, x2, C_0, f, self.medium.n_ice, self.medium.delta_n, self.medium.z_0, self.attenuation_model_int) - + self.__logger.debug(tmp) tmp_attenuation = np.ones_like(frequency) tmp_attenuation[mask] = np.interp(frequency[mask], freqs, tmp) - + else: x2_mirrored = self.get_z_mirrored(x1, x2, C_0) @@ -633,7 +633,7 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, def dt(t, C_0, frequency): z = self.get_z_unmirrored(t, C_0) return self.ds(t, C_0) / attenuation_util.get_attenuation_length(z, frequency, self.attenuation_model) - + # to speed up things we only calculate the attenuation for a few frequencies # and interpolate linearly between them mask = frequency > 0 @@ -642,41 +642,41 @@ def dt(t, C_0, frequency): self.__logger.info("_use_optimized_calculation", self._use_optimized_calculation) if self._use_optimized_calculation: - # The integration of the attenuation factor along the path with scipy.quad is inefficient. The + # The integration of the attenuation factor along the path with scipy.quad is inefficient. The # reason for this is that attenuation profile is varying a lot with depth. Hence, to improve - # performance we sum over discrete segments with loss of some accuracy. + # performance we sum over discrete segments with loss of some accuracy. # However, when a path becomes to horizontal (i.e., at the turning point of an refracted ray) # the calculation via a discrete sum becomes to inaccurate. This is because we describe the # path as a function of dz (vertical distance) and have the sum over discrete bins in dz. To avoid that - # we fall back to a numerical integration with scipy.quad only around the turning point. However, instead + # we fall back to a numerical integration with scipy.quad only around the turning point. However, instead # of integrating over dt (the exponent of the attenuation factor), we integrate only over ds (the path length) - # and evaluate the attenuation (as function of depth and frequency) for the central depth of this segment + # and evaluate the attenuation (as function of depth and frequency) for the central depth of this segment # (because this is what takes time) # For more details and comparisons see PR #507 : https://github.com/nu-radio/NuRadioMC/pull/507 - # define the width of the vertical (!) segments over which we sum. + # define the width of the vertical (!) segments over which we sum. # Since we use linspace the actual width will differ slightly dx = 10 # define the vertical window around a turning point within we will fall back to a numerical integration int_window_size = 20 - + # Check if a turning point "z_turn" is within our ray path or close to it (i.e., right behind the antenna) # if so we need to fallback to numerical integration fallback = False if x1[1] - int_window_size / 2 < z_turn and z_turn < x2_mirrored[1] + int_window_size / 2: fallback = True - + if fallback: # If we need the fallback, make sure that the turning point is in the middle of a segment (unless it is at # the edge of a path). Otherwise the segment next to the turning point will be inaccurate - int_window = [max(x1[1], z_turn - int_window_size / 2), + int_window = [max(x1[1], z_turn - int_window_size / 2), min(z_turn + int_window_size / 2, x2_mirrored[1])] # Merge two arrays which start and stop at int_window (and thus include it). The width might be slightly different segments = np.append(get_segments(x1[1], int_window[0], dx), get_segments(int_window[1], x2_mirrored[1], dx)) else: segments = get_segments(x1[1], x2_mirrored[1], dx) - + # get the actual width of each segment and their center dx_actuals = np.diff(segments) mid_points = segments[:-1] + dx_actuals / 2 @@ -691,19 +691,19 @@ def dt(t, C_0, frequency): # find segment which contains z_turn idx = np.digitize(z_turn, segments) - 1 - + # if z_turn is outside of segments if idx == len(segments) - 1: idx -= 1 elif idx == -1: idx = 0 - + att_int = np.array( [integrate.quad(self.ds, segments[idx], segments[idx + 1], args=(C_0), epsrel=1e-2, points=[z_turn])[0] / attenuation_util.get_attenuation_length(z_turn, f, self.attenuation_model) for f in freqs]) - + attenuation_exp_tmp[:, idx] = att_int - + # sum over all segments attenuation_exp = np.sum(attenuation_exp_tmp, axis=1) @@ -711,26 +711,25 @@ def dt(t, C_0, frequency): points = None if x1[1] < z_turn and z_turn < x2_mirrored[1]: points = [z_turn] - + attenuation_exp = np.array([integrate.quad(dt, x1[1], x2_mirrored[1], args=( C_0, f), epsrel=1e-2, points=points)[0] for f in freqs]) tmp = np.exp(-1 * attenuation_exp) - + tmp_attenuation = np.ones_like(frequency) tmp_attenuation[mask] = np.interp(frequency[mask], freqs, tmp) self.__logger.info("calculating attenuation from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = ({:.0f}, {:.0f}) = a factor {}".format( x1[0], x1[1], x2[0], x2[1], x2_mirrored[0], x2_mirrored[1], 1 / tmp_attenuation)) - + iF = len(frequency) // 3 output += f"adding attenuation for path segment {iS:d} -> {tmp_attenuation[iF]:.2g} at {frequency[iF]/units.MHz:.0f} MHz, " - + attenuation *= tmp_attenuation - + self.__logger.info(output) return attenuation - def get_path_segments(self, x1, x2, C_0, reflection=0, reflection_case=1): """ Calculates the different segments of the path that makes up the full ray tracing path @@ -903,9 +902,16 @@ def get_path(self, x1, x2, C_0, n_points=1000): gamma = self.get_gamma(z[mask]) zs[mask] = z[mask] res[mask] = self.get_y(gamma, C_0, C_1) - gamma = self.get_gamma(2 * z_turn - z[~mask]) - res[~mask] = 2 * y_turn - self.get_y(gamma, C_0, C_1) - zs[~mask] = 2 * z_turn - z[~mask] + if x2[1] > 0: # treat ice to air case + zenith_reflection = self.get_reflection_angle(x1, x2, C_0) + n_1 = self.medium.get_index_of_refraction([y_turn, 0, z_turn]) + zenith_air = NuRadioReco.utilities.geometryUtilities.get_fresnel_angle(zenith_reflection, n_1=n_1, n_2=1) + zs[~mask] = z[~mask] + res[~mask] = zs[~mask] * np.tan(zenith_air) + y_turn + else: + gamma = self.get_gamma(2 * z_turn - z[~mask]) + res[~mask] = 2 * y_turn - self.get_y(gamma, C_0, C_1) + zs[~mask] = 2 * z_turn - z[~mask] self.__logger.debug('turning points for C_0 = {:.2f}, b= {:.2f}, gamma = {:.4f}, z = {:.1f}, y_turn = {:.0f}'.format( C_0, self.__b, gamma_turn, z_turn, y_turn)) @@ -1023,7 +1029,7 @@ def get_delta_y(self, C_0, x1, x2, C0range=None, reflection=0, reflection_case=2 if(reflection > 0 and reflection_case == 2): y_turn = self.get_y_turn(C_0, x1) dy = y_turn - x1[0] - self.__logger.debug("relaction case 2: shifting x1 {} to {}".format(x1, x1[0] - 2 * dy)) + self.__logger.debug("reflection case 2: shifting x1 {} to {}".format(x1, x1[0] - 2 * dy)) x1[0] = x1[0] - 2 * dy for i in range(reflection): @@ -1052,7 +1058,7 @@ def get_delta_y(self, C_0, x1, x2, C0range=None, reflection=0, reflection_case=2 # 3) reflected ray, i.e. after the ray reaches the surface gamma_turn, z_turn = self.get_turning_point(c) y_turn = self.get_y(gamma_turn, C_0, C_1) - if(z_turn < x2[1]): # turning points is deeper that x2 positions, can't reach target + if(z_turn < min(x2[1], 0)): # turning points is deeper that x2 positions, can't reach target # the minimizer has problems finding the minimum if inf is returned here. Therefore, we return the distance # between the turning point and the target point + 10 x the distance between the z position of the turning points # and the target position. This results in a objective function that has the solutions as the only minima and @@ -1076,18 +1082,35 @@ def get_delta_y(self, C_0, x1, x2, C0range=None, reflection=0, reflection_case=2 'we have a direct ray, y({:.1f}) = {:.1f} -> {:.1f} away from {:.1f}, turning point = y={:.1f}, z={:.2f}, x0 = {:.1f} {:.1f}'.format(x2[1], y2_fit, diff, x2[0], y_turn, z_turn, x1[0], x1[1])) return diff else: - # now it's a bit more complicated. we need to transform the coordinates to - # be on the mirrored part of the function - z_mirrored = x2[1] - gamma = self.get_gamma(z_mirrored) - self.__logger.debug("get_y( {}, {}, {})".format(gamma, C_0, C_1)) - y2_raw = self.get_y(gamma, C_0, C_1) - y2_fit = 2 * y_turn - y2_raw - diff = (x2[0] - y2_fit) - self.__logger.debug('we have a reflected/refracted ray, y({:.1f}) = {:.1f} ({:.1f}) -> {:.1f} away from {:.1f} (gamma = {:.5g})'.format( - z_mirrored, y2_fit, y2_raw, diff, x2[0], gamma)) - return -1 * diff + if(x2[1] > 0): # first treat the ice to air case + # Do nothing if ray is refracted. If ray is reflected, don't mirror but do straight line upwards + if(z_turn == 0): + zenith_reflection = self.get_reflection_angle(x1, x2, C_0, reflection, reflection_case) + n_1 = self.medium.get_index_of_refraction([y_turn, 0, z_turn]) + zenith_air = NuRadioReco.utilities.geometryUtilities.get_fresnel_angle(zenith_reflection, n_1=n_1, n_2=1) + if(zenith_air is None): + diff = x2[0] + self.__logger.debug(f"not refracting into air") + return diff + z = (x2[0] - y_turn) / np.tan(zenith_air) + diff = x2[1] - z + self.__logger.debug(f"touching surface at {zenith_reflection/units.deg:.1f}deg -> {zenith_air/units.deg:.1f}deg -> x2 = {x2} diff {diff:.2f}") + return diff + else: + # now it's a bit more complicated. we need to transform the coordinates to + # be on the mirrored part of the function + z_mirrored = x2[1] + gamma = self.get_gamma(z_mirrored) + self.__logger.debug("get_y( {}, {}, {})".format(gamma, C_0, C_1)) + y2_raw = self.get_y(gamma, C_0, C_1) + y2_fit = 2 * y_turn - y2_raw + diff = (x2[0] - y2_fit) + + self.__logger.debug('we have a reflected/refracted ray, y({:.1f}) = {:.1f} ({:.1f}) -> {:.1f} away from {:.1f} (gamma = {:.5g})'.format( + z_mirrored, y2_fit, y2_raw, diff, x2[0], gamma)) + + return -1 * diff def determine_solution_type(self, x1, x2, C_0): """ returns the type of the solution @@ -1162,6 +1185,27 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): results = [] C0s = [] # intermediate storage of results + if(x2[1] > 0): + # special case of ice to air ray tracing. There is always one unique solution between C_0 = inf and C_0 that + # skims the surface. Therefore, we can find the solution using an efficient root finding algorithm. + logC0_start = 1 # infinity is bad, 1 is steep enough + C_0_stop, th_start = self.get_surf_skim_angle(x1) + C_0_stop /= 2 # for some reason, the get_surf_skim_angle function does not give the correct angle. The launch angle + # needs to be slightly steeper + logC0_stop = np.log(C_0_stop) + result = optimize.brentq(self.obj_delta_y, logC0_start, logC0_stop, args=(x1, x2, reflection, reflection_case)) + + C_0 = self.get_C0_from_log(result) + C0s.append(C_0) + solution_type = self.determine_solution_type(x1, x2, C_0) + self.__logger.info("found {} solution C0 = {:.2f}".format(solution_types[solution_type], C_0)) + results.append({'type': solution_type, + 'C0': C_0, + 'C1': self.get_C_1(x1, C_0), + 'reflection': reflection, + 'reflection_case': reflection_case}) + return results + # calculate optimal start value. The objective function becomes infinity if the turning point is below the z # position of the observer. We calculate the corresponding value so that the minimization starts at one edge # of the objective function @@ -1697,7 +1741,7 @@ class describing the index-of-refraction profile self.__logger.setLevel(log_level) from NuRadioMC.utilities.medium_base import IceModelSimple - if not isinstance(medium,IceModelSimple): + if not isinstance(medium, IceModelSimple): self.__logger.error("The analytic raytracer can only handle ice model of the type 'IceModelSimple'") raise TypeError("The analytic raytracer can only handle ice model of the type 'IceModelSimple'") @@ -1709,7 +1753,7 @@ class describing the index-of-refraction profile config=config, detector=detector) self.set_config(config=config) - + self._r2d = ray_tracing_2D(self._medium, self._attenuation_model, log_level=log_level, n_frequencies_integration=self._n_frequencies_integration, **ray_tracing_2D_kwards) @@ -1751,8 +1795,8 @@ def set_start_and_end_point(self, x1, x2): if(self._X2[2] < self._X1[2]): self._swap = True self.__logger.debug('swap = True') - self._X2 = np.array(x1, dtype =float) - self._X1 = np.array(x2, dtype =float) + self._X2 = np.array(x1, dtype=float) + self._X1 = np.array(x2, dtype=float) dX = self._X2 - self._X1 self._dPhi = -np.arctan2(dX[1], dX[0]) @@ -2089,7 +2133,7 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): lauVec1 = self._r1.get_launch_vector(iS) lauAng1 = np.arccos(lauVec1[2] / np.sqrt(lauVec1[0] ** 2 + lauVec1[1] ** 2 + lauVec1[2] ** 2)) focusing = np.sqrt(distance / np.sin(recAng) * np.abs((lauAng1 - lauAng) / (recPos1[2] - recPos[2]))) - if (self.get_results()[iS]['reflection'] != self._r1.get_results()[iS]['reflection'] + if (self.get_results()[iS]['reflection'] != self._r1.get_results()[iS]['reflection'] or self.get_results()[iS]['reflection_case'] != self._r1.get_results()[iS]['reflection_case']): self.__logger.error("Number or type of reflections are different between solutions - focusing correction may not be reliable.") else: From a429a8b09f92f1665b1a41f869f7c021c7660bbb Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 4 Aug 2023 12:26:06 +0000 Subject: [PATCH 04/54] add example of ice to air tracing --- NuRadioMC/SignalProp/examples/E02ToAir.py | 71 +++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 NuRadioMC/SignalProp/examples/E02ToAir.py diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py new file mode 100644 index 000000000..a9661e74e --- /dev/null +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -0,0 +1,71 @@ +import matplotlib.pyplot as plt +import numpy as np +import time +from NuRadioMC.SignalProp import analyticraytracing as ray +from NuRadioReco.utilities import units +from NuRadioMC.utilities import medium +import logging +from radiotools import helper as hp +from radiotools import plthelpers as php +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('raytracing') +# ray.cpp_available=False + +x1 = np.array([0, 0., -149.]) * units.m +x2 = np.array([200, 0., 100]) * units.m +x3 = np.array([500, 0., 100]) * units.m +x4 = np.array([1000, 0., 100]) * units.m +x5 = np.array([10000, 0., 100]) * units.m +N = 4 + +receive_vectors = np.zeros((N, 2, 3)) * np.nan +ray_tracing_C0 = np.zeros((N, 2)) * np.nan +ray_tracing_C1 = np.zeros((N, 2)) * np.nan +ray_tracing_solution_type = np.zeros((N, 2), dtype=int) * np.nan +travel_times = np.zeros((N, 2)) * np.nan +travel_distances = np.zeros((N, 2)) * np.nan + +ice = medium.southpole_simple() + +lss = ['-', '--', ':'] +colors = ['b', 'g', 'r'] +fig, ax = plt.subplots(1, 1) +ax.plot(x1[0], x1[2], 'ko') +for i, x in enumerate([x2, x3, x4, x5]): + print('finding solutions for ', x) + r = ray.ray_tracing(ice, log_level=logging.DEBUG) + r.set_start_and_end_point(x1, x) + r.find_solutions() + if(r.has_solution()): + for iS in range(r.get_number_of_solutions()): + ray_tracing_C0[i, iS] = r.get_results()[iS]['C0'] + ray_tracing_solution_type[i, iS] = r.get_solution_type(iS) + print(" Solution %d, Type %d: " % (iS, ray_tracing_solution_type[i, iS])) + R = r.get_path_length(iS) # calculate path length + T = r.get_travel_time(iS) # calculate travel time + print(" Ray Distance %.3f and Travel Time %.3f" % (R / units.m, T / units.ns)) + receive_vector = r.get_receive_vector(iS) + receive_vectors[i, iS] = receive_vector + zenith, azimuth = hp.cartesian_to_spherical(*receive_vector) + print(" Receiving Zenith %.3f and Azimuth %.3f " % (zenith / units.deg, azimuth / units.deg)) + + # to readout the actual trace, we have to flatten to 2D + dX = x - x1 + dPhi = -np.arctan2(dX[1], dX[0]) + c, s = np.cos(dPhi), np.sin(dPhi) + R = np.array(((c, -s, 0), (s, c, 0), (0, 0, 1))) + X1r = x1 + X2r = np.dot(R, x - x1) + x1 + x1_2d = np.array([X1r[0], X1r[2]]) + x2_2d = np.array([X2r[0], X2r[2]]) + r_2d = ray.ray_tracing_2D(ice) + yy, zz = r_2d.get_path(x1_2d, x2_2d, ray_tracing_C0[i, iS]) + ax.plot(yy, zz, '{}'.format(php.get_color_linestyle(i)), label='{} C0 = {:.4f}'.format(ray_tracing_solution_type[i, iS], ray_tracing_C0[i, iS])) + ax.plot(x2_2d[0], x2_2d[1], '{}{}-'.format('d', php.get_color(i))) + +ax.legend() +ax.set_xlabel("y [m]") +ax.set_ylabel("z [m]") +fig.tight_layout() +fig.savefig('example_to_air.png') +plt.show() From fd05d4af6bbbfa12e94d875d92f4652aaeba0bb2 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 4 Aug 2023 12:37:50 +0000 Subject: [PATCH 05/54] make use of CPP version optional --- NuRadioMC/SignalProp/examples/E02ToAir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index a9661e74e..ff39d3bf5 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -33,7 +33,7 @@ ax.plot(x1[0], x1[2], 'ko') for i, x in enumerate([x2, x3, x4, x5]): print('finding solutions for ', x) - r = ray.ray_tracing(ice, log_level=logging.DEBUG) + r = ray.ray_tracing(ice, log_level=logging.DEBUG, use_cpp=False) r.set_start_and_end_point(x1, x) r.find_solutions() if(r.has_solution()): From b7424efdf44d402b02be672dd41aac92d2c9a985 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Sat, 5 Aug 2023 06:07:15 +0000 Subject: [PATCH 06/54] add calculation of travel times and propagation distances for ice to air case --- NuRadioMC/SignalProp/analyticraytracing.py | 95 +++++++++++++++------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 4ba7bccb3..485fd7f26 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -40,7 +40,6 @@ print("check NuRadioMC/NuRadioMC/SignalProp/CPPAnalyticRayTracing for manual compilation") cpp_available = False -cpp_available = False """ analytic ray tracing solution @@ -94,7 +93,8 @@ def __init__(self, medium, attenuation_model="SP1", log_level=logging.WARNING, n_frequencies_integration=25, use_optimized_start_values=False, - overwrite_speedup=None): + overwrite_speedup=None, + use_cpp=cpp_available): """ initialize 2D analytic ray tracing class @@ -119,6 +119,9 @@ def __init__(self, medium, attenuation_model="SP1", speedup_attenuation_models (i.e., "GL3"). With this argument you can explicitly activate or deactivate (True or False) if you want to use the optimization. (Default: None, i.e., use optimization if ice model is listed in speedup_attenuation_models) + use_cpp: bool + if True, use CPP implementation of minimization routines + default: True if CPP version is available """ self.medium = medium @@ -138,6 +141,7 @@ def __init__(self, medium, attenuation_model="SP1", self._use_optimized_calculation = self.attenuation_model in speedup_attenuation_models if overwrite_speedup is not None: self._use_optimized_calculation = overwrite_speedup + self.use_cpp = use_cpp def n(self, z): """ @@ -437,18 +441,32 @@ def get_path_direct(z1, z2): # z0 above z_deep, z1 below z_deep return int2 - int1 - int_diff - if(solution_type == 1): - tmp += get_path_direct(x1[1], x2[1]) - else: - if(solution_type == 3): - z_turn = 0 - else: - gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) - # print('solution type {:d}, zturn = {:.1f}'.format(solution_type, z_turn)) + # first treat special case of ice to air propagation + if(x2[1] > 0): + z_turn = 0 + y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) + d_air = ((x2[0] - y_turn) ** 2 + (x2[1]) ** 2) ** 0.5 try: - tmp += get_path_direct(x1[1], z_turn) + get_path_direct(x2[1], z_turn) + ttmp = get_path_direct(x1[1], z_turn) + tmp += ttmp + d_air + self.__logger.info("calculating travel distance from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = {:.2f} m in ice + {:.2f} m in air".format( + x1[0], x1[1], x2[0], x2[1], ttmp / units.m, d_air / units.m)) except: tmp += None + + else: + if(solution_type == 1): + tmp += get_path_direct(x1[1], x2[1]) + else: + if(solution_type == 3): + z_turn = 0 + else: + gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) + # print('solution type {:d}, zturn = {:.1f}'.format(solution_type, z_turn)) + try: + tmp += get_path_direct(x1[1], z_turn) + get_path_direct(x2[1], z_turn) + except: + tmp += None return tmp @@ -558,24 +576,38 @@ def get_ToF_direct(z1, z2): # z0 above z_deep, z1 below z_deep return int2 - int1 - int_diff - if(solution_type == 1): - ttmp = get_ToF_direct(x1[1], x2[1]) - tmp += ttmp - self.__logger.info("calculating travel time from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = {:.2f} ns".format( - x1[0], x1[1], x2[0], x2[1], ttmp / units.ns)) - else: - if(solution_type == 3): - z_turn = 0 - else: - gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) - # print('solution type {:d}, zturn = {:.1f}'.format(solution_type, z_turn)) + # first treat special case of ice to air propagation + if(x2[1] > 0): + z_turn = 0 + y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) + t_air = ((x2[0] - y_turn) ** 2 + (x2[1]) ** 2) ** 0.5 / speed_of_light try: - ttmp = get_ToF_direct(x1[1], z_turn) + get_ToF_direct(x2[1], z_turn) + ttmp = get_ToF_direct(x1[1], z_turn) + tmp += ttmp + t_air + self.__logger.info("calculating travel time from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = {:.2f} ns in ice + {:.2f} ns in air".format( + x1[0], x1[1], x2[0], x2[1], ttmp / units.ns, t_air / units.ns)) + except: + tmp += None + + else: + if(solution_type == 1): + ttmp = get_ToF_direct(x1[1], x2[1]) tmp += ttmp self.__logger.info("calculating travel time from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = {:.2f} ns".format( x1[0], x1[1], x2[0], x2[1], ttmp / units.ns)) - except: - tmp += None + else: + if(solution_type == 3): + z_turn = 0 + else: + gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) + # print('solution type {:d}, zturn = {:.1f}'.format(solution_type, z_turn)) + try: + ttmp = get_ToF_direct(x1[1], z_turn) + get_ToF_direct(x2[1], z_turn) + tmp += ttmp + self.__logger.info("calculating travel time from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = {:.2f} ns".format( + x1[0], x1[1], x2[0], x2[1], ttmp / units.ns)) + except: + tmp += None return tmp def __get_frequencies_for_attenuation(self, frequency, max_detector_freq): @@ -613,7 +645,7 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, else: x11, x1, x22, x2, C_0, C_1 = segment - if cpp_available: + if self.use_cpp: mask = frequency > 0 freqs = self.__get_frequencies_for_attenuation(frequency, max_detector_freq) tmp = np.zeros_like(freqs) @@ -1170,7 +1202,7 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): self.__logger.error("a solution for {:d} reflection(s) off the bottom reflective layer is requested, but ice model does not specify a reflective layer".format(reflection)) raise AttributeError("a solution for {:d} reflection(s) off the bottom reflective layer is requested, but ice model does not specify a reflective layer".format(reflection)) - if(cpp_available): + if(self.use_cpp): # t = time.time() # print("find solutions", x1, x2, self.medium.n_ice, self.medium.delta_n, self.medium.z_0, reflection, reflection_case, self.medium.reflection) tmp_reflection = copy.copy(self.medium.reflection) @@ -1689,7 +1721,8 @@ class ray_tracing(ray_tracing_base): def __init__(self, medium, attenuation_model="SP1", log_level=logging.WARNING, n_frequencies_integration=100, n_reflections=0, config=None, - detector=None, ray_tracing_2D_kwards={}): + detector=None, ray_tracing_2D_kwards={}, + use_cpp=cpp_available): """ class initilization @@ -1736,6 +1769,10 @@ class describing the index-of-refraction profile ray_tracing_2D_kwards: dict Additional arguments which are passed to ray_tracing_2D + use_cpp: bool + if True, use CPP implementation of minimization routines + default: True if CPP version is available + """ self.__logger = logging.getLogger('ray_tracing_analytic') self.__logger.setLevel(log_level) @@ -1756,7 +1793,7 @@ class describing the index-of-refraction profile self._r2d = ray_tracing_2D(self._medium, self._attenuation_model, log_level=log_level, n_frequencies_integration=self._n_frequencies_integration, - **ray_tracing_2D_kwards) + **ray_tracing_2D_kwards, use_cpp=use_cpp) self._swap = None self._dPhi = None From ba1b0dc250075522a11df6b89c1029dfe6ebd28e Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Sat, 5 Aug 2023 06:23:54 +0000 Subject: [PATCH 07/54] add calculation of travel and propagation times/distances to numerical integrator --- NuRadioMC/SignalProp/analyticraytracing.py | 57 +++++++++++++++------- NuRadioMC/SignalProp/examples/E02ToAir.py | 4 +- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 485fd7f26..667d93a51 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -354,16 +354,28 @@ def get_path_length(self, x1, x2, C_0, reflection=0, reflection_case=1): x1 = x1t else: x11, x1, x22, x2, C_0, C_1 = segment - x2_mirrored = self.get_z_mirrored(x1, x2, C_0) - gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) + + # first treat special case of ice to air propagation + z_int = None points = None - if(x1[1] < z_turn and z_turn < x2_mirrored[1]): - points = [z_turn] - path_length = integrate.quad(self.ds, x1[1], x2_mirrored[1], args=(C_0), points=points, epsabs=1e-4, epsrel=1.49e-08, limit=50) - self.__logger.info("calculating path length ({}) from ({:.0f}, {:.0f}) to ({:.2f}, {:.2f}) = ({:.2f}, {:.2f}) = {:.2f} m".format(solution_types[self.determine_solution_type(x1, x2, C_0)], x1[0], x1[1], x2[0], x2[1], - x2_mirrored[0], - x2_mirrored[1], - path_length[0] / units.m)) + if(x2[1] > 0): + # we need to integrat only until the ray touches the surface + z_turn = 0 + y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) + d_air = ((x2[0] - y_turn) ** 2 + (x2[1]) ** 2) ** 0.5 + tmp += d_air + z_int = z_turn + self.__logger.info(f"adding additional propagation path through air of {d_air/units.m:.1f}m") + else: + x2_mirrored = self.get_z_mirrored(x1, x2, C_0) + gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) + if(x1[1] < z_turn and z_turn < x2_mirrored[1]): + points = [z_turn] + z_int = x2_mirrored[1] + path_length = integrate.quad(self.ds, x1[1], z_int, args=(C_0), points=points, + epsabs=1e-4, epsrel=1.49e-08, limit=50) + self.__logger.info(f"calculating path length ({solution_types[self.determine_solution_type(x1, x2, C_0)]}) \ + from ({x1[0]:.0f}, {x1[1]:.0f}) to ({x2[0]:.2f}, {x2[1]:.2f}) = {path_length[0] / units.m:.2f} m") tmp += path_length[0] return tmp @@ -483,18 +495,29 @@ def get_travel_time(self, x1, x2, C_0, reflection=0, reflection_case=1): x1 = x1t else: x11, x1, x22, x2, C_0, C_1 = segment - + + # first treat special case of ice to air propagation + z_int = None + points = None x2_mirrored = self.get_z_mirrored(x1, x2, C_0) - + if(x2[1] > 0): + # we need to integrat only until the ray touches the surface + z_turn = 0 + y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) + T_air = ((x2[0] - y_turn) ** 2 + (x2[1]) ** 2) ** 0.5 / speed_of_light + tmp += T_air + z_int = z_turn + self.__logger.info(f"adding additional propagation path through air of {T_air/units.ns:.1f}ns") + else: + gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) + if(x1[1] < z_turn and z_turn < x2_mirrored[1]): + points = [z_turn] + z_int = x2_mirrored[1] def dt(t, C_0): z = self.get_z_unmirrored(t, C_0) return self.ds(t, C_0) / speed_of_light * self.n(z) - - gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) - points = None - if(x1[1] < z_turn and z_turn < x2_mirrored[1]): - points = [z_turn] - travel_time = integrate.quad(dt, x1[1], x2_mirrored[1], args=(C_0), points=points, epsabs=1e-10, epsrel=1.49e-08, limit=500) + travel_time = integrate.quad(dt, x1[1], z_int, args=(C_0), points=points, + epsabs=1e-10, epsrel=1.49e-08, limit=500) self.__logger.info("calculating travel time from ({:.0f}, {:.0f}) to ({:.0f}, {:.0f}) = ({:.0f}, {:.0f}) = {:.2f} ns".format( x1[0], x1[1], x2[0], x2[1], x2_mirrored[0], x2_mirrored[1], travel_time[0] / units.ns)) tmp += travel_time[0] diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index ff39d3bf5..b05fb532c 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -42,8 +42,10 @@ ray_tracing_solution_type[i, iS] = r.get_solution_type(iS) print(" Solution %d, Type %d: " % (iS, ray_tracing_solution_type[i, iS])) R = r.get_path_length(iS) # calculate path length + R2 = r.get_path_length(iS, analytic=False) # calculate path length T = r.get_travel_time(iS) # calculate travel time - print(" Ray Distance %.3f and Travel Time %.3f" % (R / units.m, T / units.ns)) + T2 = r.get_travel_time(iS, analytic=False) # calculate travel time + print(f" Ray Distance {R/units.m:.3f}m {R2/units.m:.3f}m and Travel Time {T/units.ns:.3f}ns {T2/units.ns:.3f}ns") receive_vector = r.get_receive_vector(iS) receive_vectors[i, iS] = receive_vector zenith, azimuth = hp.cartesian_to_spherical(*receive_vector) From e527ed5ef15319977a9889fe3d9e83cb354226bd Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Sat, 5 Aug 2023 06:29:45 +0000 Subject: [PATCH 08/54] add info when using CPP ray tracer or not --- NuRadioMC/SignalProp/analyticraytracing.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 667d93a51..d5250d446 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -24,7 +24,7 @@ try: from NuRadioMC.SignalProp.CPPAnalyticRayTracing import wrapper cpp_available = True - print("using CPP version of ray tracer") + print("CPP version of ray tracer is available") except: print("trying to compile the CPP extension on-the-fly") try: @@ -34,7 +34,7 @@ "install.sh")) from NuRadioMC.SignalProp.CPPAnalyticRayTracing import wrapper cpp_available = True - print("compilation was successful, using CPP version of ray tracer") + print("compilation was successful, CPP version of ray tracer is available") except: print("compilation was not successful, using python version of ray tracer") print("check NuRadioMC/NuRadioMC/SignalProp/CPPAnalyticRayTracing for manual compilation") @@ -1813,6 +1813,11 @@ class describing the index-of-refraction profile config=config, detector=detector) self.set_config(config=config) + + if use_cpp: + self.__logger.warning(f"using CPP version of ray tracer") + else: + self.__logger.warning(f"using python version of ray tracer") self._r2d = ray_tracing_2D(self._medium, self._attenuation_model, log_level=log_level, n_frequencies_integration=self._n_frequencies_integration, From da0c1c351bfe9786e53c326a273c21c654ddcd94 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Sat, 5 Aug 2023 06:47:53 +0000 Subject: [PATCH 09/54] add calculation of attenuation for to air case --- NuRadioMC/SignalProp/analyticraytracing.py | 13 ++++++++++--- NuRadioMC/SignalProp/examples/E02ToAir.py | 3 +++ NuRadioMC/SignalProp/examples/example_3d.py | 2 ++ NuRadioMC/test/SignalProp/T01test_python_vs_cpp.py | 3 +-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index d5250d446..a70c3c972 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -667,6 +667,13 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, x1 = x1t else: x11, x1, x22, x2, C_0, C_1 = segment + + # treat special ice to air case. Attenuation in air can be neglected, so only + # calculate attenuation until the ray reaches the surface + if x2[1] > 0: + z_turn = 0 + y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) + x2 = [y_turn, z_turn] if self.use_cpp: mask = frequency > 0 @@ -694,7 +701,7 @@ def dt(t, C_0, frequency): mask = frequency > 0 freqs = self.__get_frequencies_for_attenuation(frequency, max_detector_freq) gamma_turn, z_turn = self.get_turning_point(self.medium.n_ice ** 2 - C_0 ** -2) - self.__logger.info("_use_optimized_calculation", self._use_optimized_calculation) + self.__logger.info(f"_use_optimized_calculation {self._use_optimized_calculation}") if self._use_optimized_calculation: # The integration of the attenuation factor along the path with scipy.quad is inefficient. The @@ -1815,9 +1822,9 @@ class describing the index-of-refraction profile self.set_config(config=config) if use_cpp: - self.__logger.warning(f"using CPP version of ray tracer") + self.__logger.debug(f"using CPP version of ray tracer") else: - self.__logger.warning(f"using python version of ray tracer") + self.__logger.debug(f"using python version of ray tracer") self._r2d = ray_tracing_2D(self._medium, self._attenuation_model, log_level=log_level, n_frequencies_integration=self._n_frequencies_integration, diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index b05fb532c..a8a54f633 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -51,6 +51,9 @@ zenith, azimuth = hp.cartesian_to_spherical(*receive_vector) print(" Receiving Zenith %.3f and Azimuth %.3f " % (zenith / units.deg, azimuth / units.deg)) + att = r.get_attenuation(iS, np.array([100, 200]) * units.MHz) + print(att) + # to readout the actual trace, we have to flatten to 2D dX = x - x1 dPhi = -np.arctan2(dX[1], dX[0]) diff --git a/NuRadioMC/SignalProp/examples/example_3d.py b/NuRadioMC/SignalProp/examples/example_3d.py index 43d43ce9a..a78a006bc 100644 --- a/NuRadioMC/SignalProp/examples/example_3d.py +++ b/NuRadioMC/SignalProp/examples/example_3d.py @@ -43,6 +43,8 @@ R = r.get_path_length(iS) # calculate path length T = r.get_travel_time(iS) # calculate travel time print(" Ray Distance %.3f and Travel Time %.3f" % (R / units.m, T / units.ns)) + + receive_vector = r.get_receive_vector(iS) receive_vectors[i, iS] = receive_vector zenith, azimuth = hp.cartesian_to_spherical(*receive_vector) diff --git a/NuRadioMC/test/SignalProp/T01test_python_vs_cpp.py b/NuRadioMC/test/SignalProp/T01test_python_vs_cpp.py index 9664a2b48..68a7be284 100644 --- a/NuRadioMC/test/SignalProp/T01test_python_vs_cpp.py +++ b/NuRadioMC/test/SignalProp/T01test_python_vs_cpp.py @@ -50,10 +50,9 @@ results_C0s_python = np.zeros((n_events, 2)) results_A_python = np.zeros((n_events, 2, n_freqs)) -ray.cpp_available = False t_start = time.time() for iX, x in enumerate(points): - r = ray.ray_tracing(ice) + r = ray.ray_tracing(ice, use_cpp=False) r.set_start_and_end_point(x, x_receiver) r.find_solutions() if(r.has_solution()): From d90079cce2b59635e4da6565f127553eae81cb45 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 22 Aug 2023 18:07:33 +0200 Subject: [PATCH 10/54] fix dy/dz in air, use_cpp inherited also for focusing correction, promote focusing correction failure to warning --- NuRadioMC/SignalProp/analyticraytracing.py | 43 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index a70c3c972..759156a11 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -252,7 +252,14 @@ def get_y_diff(self, z_raw, C_0): """ derivative dy(z)/dz """ - + correct_for_air = False + # if we are above the ice surface, the analytic expression below + # does not apply. Therefore, we instead calculate dy/dz at z=0 where it is still valid + # and then correct this using Snell's law + if z_raw > 0: # we are above the ice surface, where the below expression does not apply + z_raw_copy = copy.copy(z_raw) + z_raw = 0 + correct_for_air = True z = self.get_z_unmirrored(z_raw, C_0) c = self.medium.n_ice ** 2 - C_0 ** -2 B = (0.2e1 * np.sqrt(c) * np.sqrt(-self.__b * self.medium.delta_n * np.exp(z / self.medium.z_0) + self.medium.delta_n ** @@ -263,7 +270,12 @@ def get_y_diff(self, z_raw, C_0): E = (E1 + E2 + c) res = (-np.sqrt(c) * np.exp(z / self.medium.z_0) * self.__b * self.medium.delta_n + 0.2e1 * np.sqrt(-self.__b * self.medium.delta_n * np.exp(z / self.medium.z_0) + self.medium.delta_n ** 2 * np.exp(0.2e1 * z / self.medium.z_0) + c) * c + 0.2e1 * c ** 1.5) / B * E ** -0.5 * (D ** (-0.5)) - + if correct_for_air: + n_surface = self.medium.get_index_of_refraction([0,0,0]) + theta_ice = np.arctan(res) + theta_air = np.arcsin(n_surface * np.sin(theta_ice)) + res = np.tan(theta_air) + z_raw = z_raw_copy if(z != z_raw): res *= -1 return res @@ -1251,10 +1263,15 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): # special case of ice to air ray tracing. There is always one unique solution between C_0 = inf and C_0 that # skims the surface. Therefore, we can find the solution using an efficient root finding algorithm. logC0_start = 1 # infinity is bad, 1 is steep enough - C_0_stop, th_start = self.get_surf_skim_angle(x1) - C_0_stop /= 2 # for some reason, the get_surf_skim_angle function does not give the correct angle. The launch angle - # needs to be slightly steeper - logC0_stop = np.log(C_0_stop) + C_0_stop = self.get_C_0_from_angle(np.arcsin(1/self.medium.get_index_of_refraction([0, x1[0], x1[1]])), x1[1]).x[0] + # C_0_stop, th_start = self.get_surf_skim_angle(x1) + # C_0_stop *= (1-1e-8) # for some reason, the get_surf_skim_angle function does not give the correct angle. The launch angle + # # needs to be slightly steeper + logC0_stop = np.log(C_0_stop - 1/self.medium.n_ice) + self.__logger.warning( + "C0: {}, {} start: {} stop: {}".format( + logC0_start, logC0_stop, *[self.obj_delta_y(logC0, x1, x2, reflection, reflection_case) for logC0 in [logC0_start, logC0_stop]] + )) result = optimize.brentq(self.obj_delta_y, logC0_start, logC0_stop, args=(x1, x2, reflection, reflection_case)) C_0 = self.get_C0_from_log(result) @@ -1821,6 +1838,7 @@ class describing the index-of-refraction profile detector=detector) self.set_config(config=config) + self.use_cpp = use_cpp if use_cpp: self.__logger.debug(f"using CPP version of ray tracer") else: @@ -2179,10 +2197,13 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): focusing: float gain of the signal at the receiver due to the focusing effect """ + self.__logger.warning("We're trying to focus here!") recVec = self.get_receive_vector(iS) recVec = -1.0 * recVec + # recAng = np.arcsin(recVec[2] / np.linalg.norm(recVec)) recAng = np.arccos(recVec[2] / np.sqrt(recVec[0] ** 2 + recVec[1] ** 2 + recVec[2] ** 2)) lauVec = self.get_launch_vector(iS) + # lauAng = np.arcsin(lauVec[2] / np.linalg.norm(lauVec)) lauAng = np.arccos(lauVec[2] / np.sqrt(lauVec[0] ** 2 + lauVec[1] ** 2 + lauVec[2] ** 2)) distance = self.get_path_length(iS) # we need to be careful here. If X1 (the emitter) is above the X2 (the receiver) the positions are swapped @@ -2198,19 +2219,25 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): recPos1 = np.array([self._X2[0], self._X2[1], self._X2[2] + dz]) if not hasattr(self, "_r1"): self._r1 = ray_tracing(self._medium, self._attenuation_model, logging.WARNING, - self._n_frequencies_integration, self._n_reflections) + self._n_frequencies_integration, self._n_reflections, use_cpp=self.use_cpp) self._r1.set_start_and_end_point(vetPos, recPos1) self._r1.find_solutions() if iS < self._r1.get_number_of_solutions(): lauVec1 = self._r1.get_launch_vector(iS) + # lauAng1 = np.arcsin(lauVec1[2] / np.linalg.norm(lauVec1)) lauAng1 = np.arccos(lauVec1[2] / np.sqrt(lauVec1[0] ** 2 + lauVec1[1] ** 2 + lauVec1[2] ** 2)) + self.__logger.warning( + "focusing: receive angle {:.2f} / launch angle {:.2f} / d_launch_angle {:.4f}".format( + recAng / units.deg, lauAng / units.deg, (lauAng1-lauAng) / units.deg + ) + ) focusing = np.sqrt(distance / np.sin(recAng) * np.abs((lauAng1 - lauAng) / (recPos1[2] - recPos[2]))) if (self.get_results()[iS]['reflection'] != self._r1.get_results()[iS]['reflection'] or self.get_results()[iS]['reflection_case'] != self._r1.get_results()[iS]['reflection_case']): self.__logger.error("Number or type of reflections are different between solutions - focusing correction may not be reliable.") else: focusing = 1.0 - self.__logger.info("too few ray tracing solutions, setting focusing factor to 1") + self.__logger.warning("too few ray tracing solutions, setting focusing factor to 1") self.__logger.debug(f'amplification due to focusing of solution {iS:d} = {focusing:.3f}') if(focusing > limit): self.__logger.info(f"amplification due to focusing is {focusing:.1f}x -> limiting amplification factor to {limit:.1f}x") From 87bd2bbcd02a405f0f250180fbb426d888d322f1 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 22 Aug 2023 18:09:47 +0200 Subject: [PATCH 11/54] forgot to change one logger output to debug [ci skip] --- NuRadioMC/SignalProp/analyticraytracing.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 759156a11..0b0f7acda 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -1264,9 +1264,6 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): # skims the surface. Therefore, we can find the solution using an efficient root finding algorithm. logC0_start = 1 # infinity is bad, 1 is steep enough C_0_stop = self.get_C_0_from_angle(np.arcsin(1/self.medium.get_index_of_refraction([0, x1[0], x1[1]])), x1[1]).x[0] - # C_0_stop, th_start = self.get_surf_skim_angle(x1) - # C_0_stop *= (1-1e-8) # for some reason, the get_surf_skim_angle function does not give the correct angle. The launch angle - # # needs to be slightly steeper logC0_stop = np.log(C_0_stop - 1/self.medium.n_ice) self.__logger.warning( "C0: {}, {} start: {} stop: {}".format( @@ -2197,13 +2194,10 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): focusing: float gain of the signal at the receiver due to the focusing effect """ - self.__logger.warning("We're trying to focus here!") recVec = self.get_receive_vector(iS) recVec = -1.0 * recVec - # recAng = np.arcsin(recVec[2] / np.linalg.norm(recVec)) recAng = np.arccos(recVec[2] / np.sqrt(recVec[0] ** 2 + recVec[1] ** 2 + recVec[2] ** 2)) lauVec = self.get_launch_vector(iS) - # lauAng = np.arcsin(lauVec[2] / np.linalg.norm(lauVec)) lauAng = np.arccos(lauVec[2] / np.sqrt(lauVec[0] ** 2 + lauVec[1] ** 2 + lauVec[2] ** 2)) distance = self.get_path_length(iS) # we need to be careful here. If X1 (the emitter) is above the X2 (the receiver) the positions are swapped @@ -2224,9 +2218,8 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): self._r1.find_solutions() if iS < self._r1.get_number_of_solutions(): lauVec1 = self._r1.get_launch_vector(iS) - # lauAng1 = np.arcsin(lauVec1[2] / np.linalg.norm(lauVec1)) lauAng1 = np.arccos(lauVec1[2] / np.sqrt(lauVec1[0] ** 2 + lauVec1[1] ** 2 + lauVec1[2] ** 2)) - self.__logger.warning( + self.__logger.debug( "focusing: receive angle {:.2f} / launch angle {:.2f} / d_launch_angle {:.4f}".format( recAng / units.deg, lauAng / units.deg, (lauAng1-lauAng) / units.deg ) From dbe19e38688e8330d8613e85792f3c1bc4c4f57e Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Mon, 28 Aug 2023 14:37:18 +0200 Subject: [PATCH 12/54] add kwarg in_air get_y_diff and other ray tracing functions to distinguish ice-to-air case --- NuRadioMC/SignalProp/analyticraytracing.py | 39 +++++++++++----------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 0b0f7acda..3fb6140c0 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -248,7 +248,7 @@ def get_y(self, gamma, C_0, C_1): result = self.medium.z_0 * (self.medium.n_ice ** 2 * C_0 ** 2 - 1) ** -0.5 * np.log(logargument) + C_1 return result - def get_y_diff(self, z_raw, C_0): + def get_y_diff(self, z_raw, C_0, in_air=False): """ derivative dy(z)/dz """ @@ -256,11 +256,11 @@ def get_y_diff(self, z_raw, C_0): # if we are above the ice surface, the analytic expression below # does not apply. Therefore, we instead calculate dy/dz at z=0 where it is still valid # and then correct this using Snell's law - if z_raw > 0: # we are above the ice surface, where the below expression does not apply - z_raw_copy = copy.copy(z_raw) - z_raw = 0 + if (not in_air) or (z_raw < 0): + z = self.get_z_unmirrored(z_raw, C_0) + else: # we are above the ice surface, where the below expression does not apply + z = 0 correct_for_air = True - z = self.get_z_unmirrored(z_raw, C_0) c = self.medium.n_ice ** 2 - C_0 ** -2 B = (0.2e1 * np.sqrt(c) * np.sqrt(-self.__b * self.medium.delta_n * np.exp(z / self.medium.z_0) + self.medium.delta_n ** 2 * np.exp(0.2e1 * z / self.medium.z_0) + c) - self.__b * self.medium.delta_n * np.exp(z / self.medium.z_0) + 0.2e1 * c) @@ -271,11 +271,10 @@ def get_y_diff(self, z_raw, C_0): res = (-np.sqrt(c) * np.exp(z / self.medium.z_0) * self.__b * self.medium.delta_n + 0.2e1 * np.sqrt(-self.__b * self.medium.delta_n * np.exp(z / self.medium.z_0) + self.medium.delta_n ** 2 * np.exp(0.2e1 * z / self.medium.z_0) + c) * c + 0.2e1 * c ** 1.5) / B * E ** -0.5 * (D ** (-0.5)) if correct_for_air: - n_surface = self.medium.get_index_of_refraction([0,0,0]) + n_surface = self.n(0) theta_ice = np.arctan(res) theta_air = np.arcsin(n_surface * np.sin(theta_ice)) res = np.tan(theta_air) - z_raw = z_raw_copy if(z != z_raw): res *= -1 return res @@ -865,7 +864,7 @@ def get_path_segments(self, x1, x2, C_0, reflection=0, reflection_case=1): x1 = x2 return tmp - def get_angle(self, x, x_start, C_0, reflection=0, reflection_case=1): + def get_angle(self, x, x_start, C_0, reflection=0, reflection_case=1, in_air=False): """ calculates the angle with respect to the positive z-axis of the ray path at position x @@ -890,17 +889,17 @@ def get_angle(self, x, x_start, C_0, reflection=0, reflection_case=1): x_start = last_segment[1] z = self.get_z_mirrored(x_start, x, C_0)[1] - dy = self.get_y_diff(z, C_0) + dy = self.get_y_diff(z, C_0, in_air=in_air) angle = np.arctan(dy) if(angle < 0): angle = np.pi + angle return angle def get_launch_angle(self, x1, C_0, reflection=0, reflection_case=1): - return self.get_angle(x1, x1, C_0, reflection, reflection_case) + return self.get_angle(x1, x1, C_0, reflection, reflection_case, in_air=x1[1]>0) def get_receive_angle(self, x1, x2, C_0, reflection=0, reflection_case=1): - return np.pi - self.get_angle(x2, x1, C_0, reflection, reflection_case) + return np.pi - self.get_angle(x2, x1, C_0, reflection, reflection_case, in_air=x2[1]>0) def get_reflection_angle(self, x1, x2, C_0, reflection=0, reflection_case=1): """ @@ -931,7 +930,7 @@ def get_reflection_angle(self, x1, x2, C_0, reflection=0, reflection_case=1): gamma_turn, z_turn = self.get_turning_point(c) y_turn = self.get_y_turn(C_0, x1) if((z_turn >= 0) and (y_turn > x11[0]) and (y_turn < x22[0])): # for the first track segment we need to check if turning point is right of start point (otherwise we have a downward going ray that does not have a turning point), and for the last track segment we need to check that the turning point is left of the stop position. - r = self.get_angle(np.array([y_turn, 0]), x1, C_0) + r = self.get_angle(np.array([y_turn, 0]), x1, C_0, in_air=(x2[1]>0)) self.__logger.debug( "reflecting off surface at y = {:.1f}m, reflection angle = {:.1f}deg".format(y_turn, r / units.deg)) output.append(r) @@ -1265,8 +1264,8 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): logC0_start = 1 # infinity is bad, 1 is steep enough C_0_stop = self.get_C_0_from_angle(np.arcsin(1/self.medium.get_index_of_refraction([0, x1[0], x1[1]])), x1[1]).x[0] logC0_stop = np.log(C_0_stop - 1/self.medium.n_ice) - self.__logger.warning( - "C0: {}, {} start: {} stop: {}".format( + self.__logger.debug( + "Looking for ice-air solutions between C0 ({}, {}) with delta_y ({}, {})".format( logC0_start, logC0_stop, *[self.obj_delta_y(logC0, x1, x2, reflection, reflection_case) for logC0 in [logC0_start, logC0_stop]] )) result = optimize.brentq(self.obj_delta_y, logC0_start, logC0_stop, args=(x1, x2, reflection, reflection_case)) @@ -1389,11 +1388,11 @@ def plot_result(self, x1, x2, C_0, ax): # ax.plot(x2[1], x2[0], 'd') ax.legend() - def get_angle_from_C_0(self, C_0, z_pos, angoff=0): + def get_angle_from_C_0(self, C_0, z_pos, angoff=0, in_air=False): logC_0 = np.log(C_0 - 1. / self.medium.n_ice) - return self.get_angle_from_logC_0(logC_0, z_pos, angoff) + return self.get_angle_from_logC_0(logC_0, z_pos, angoff, in_air=in_air) - def get_angle_from_logC_0(self, logC_0, z_pos, angoff=0): + def get_angle_from_logC_0(self, logC_0, z_pos, angoff=0, in_air=False): ''' argument angoff is provided so that the function can be used for minimization in get_C_0_from_angle(), @@ -1416,7 +1415,7 @@ def get_angle_from_logC_0(self, logC_0, z_pos, angoff=0): C_0 = self.get_C0_from_log(logC_0) - dydz = self.get_y_diff(z_pos, C_0) + dydz = self.get_y_diff(z_pos, C_0, in_air=in_air) # dydz = self.get_dydz_analytic(C_0, z_pos) angle = np.arctan(dydz) @@ -1424,7 +1423,7 @@ def get_angle_from_logC_0(self, logC_0, z_pos, angoff=0): return angle - angoff - def get_C_0_from_angle(self, anglaunch, z_pos): + def get_C_0_from_angle(self, anglaunch, z_pos, in_air=False): ''' Find parameter C0 corresponding to a given launch angle and z-position of a ray. @@ -1447,7 +1446,7 @@ def get_C_0_from_angle(self, anglaunch, z_pos): logC_0_start = np.log(C_0_start - 1. / self.medium.n_ice) # result = optimize.root(self.get_angle_from_C_0,np.pi/4.,args=(z_pos,anglaunch)) - result = optimize.root(self.get_angle_from_logC_0, logC_0_start, args=(z_pos, anglaunch)) + result = optimize.root(self.get_angle_from_logC_0, logC_0_start, args=(z_pos, anglaunch, 0, in_air)) # want to return the complete instance of the result class; result value result.x[0] is logC_0, # but we want C_0, so replace it in the result class. This may not be good practice but it seems to be From d48a0ef09b43ef2c4a0e6b1c9319419cfa17df88 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Fri, 1 Sep 2023 02:03:49 +0200 Subject: [PATCH 13/54] remove spurious in_air argument for reflection angle --- NuRadioMC/SignalProp/analyticraytracing.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 3fb6140c0..436d6fcd6 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -888,7 +888,10 @@ def get_angle(self, x, x_start, C_0, reflection=0, reflection_case=1, in_air=Fal last_segment = self.get_path_segments(x_start, x, C_0, reflection, reflection_case)[-1] x_start = last_segment[1] - z = self.get_z_mirrored(x_start, x, C_0)[1] + if in_air: + z = x[1] + else: + z = self.get_z_mirrored(x_start, x, C_0)[1] dy = self.get_y_diff(z, C_0, in_air=in_air) angle = np.arctan(dy) if(angle < 0): @@ -930,7 +933,7 @@ def get_reflection_angle(self, x1, x2, C_0, reflection=0, reflection_case=1): gamma_turn, z_turn = self.get_turning_point(c) y_turn = self.get_y_turn(C_0, x1) if((z_turn >= 0) and (y_turn > x11[0]) and (y_turn < x22[0])): # for the first track segment we need to check if turning point is right of start point (otherwise we have a downward going ray that does not have a turning point), and for the last track segment we need to check that the turning point is left of the stop position. - r = self.get_angle(np.array([y_turn, 0]), x1, C_0, in_air=(x2[1]>0)) + r = self.get_angle(np.array([y_turn, 0]), x1, C_0) self.__logger.debug( "reflecting off surface at y = {:.1f}m, reflection angle = {:.1f}deg".format(y_turn, r / units.deg)) output.append(r) @@ -1446,7 +1449,7 @@ def get_C_0_from_angle(self, anglaunch, z_pos, in_air=False): logC_0_start = np.log(C_0_start - 1. / self.medium.n_ice) # result = optimize.root(self.get_angle_from_C_0,np.pi/4.,args=(z_pos,anglaunch)) - result = optimize.root(self.get_angle_from_logC_0, logC_0_start, args=(z_pos, anglaunch, 0, in_air)) + result = optimize.root(self.get_angle_from_logC_0, logC_0_start, args=(z_pos, anglaunch, in_air)) # want to return the complete instance of the result class; result value result.x[0] is logC_0, # but we want C_0, so replace it in the result class. This may not be good practice but it seems to be From 2af6d443656960f6dc40da11a89228a861b4ba81 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Wed, 4 Oct 2023 11:50:24 +0200 Subject: [PATCH 14/54] add property _pre_trigger_time to Trigger class --- NuRadioReco/framework/trigger.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/NuRadioReco/framework/trigger.py b/NuRadioReco/framework/trigger.py index 1792f0916..ab1b8f156 100644 --- a/NuRadioReco/framework/trigger.py +++ b/NuRadioReco/framework/trigger.py @@ -59,6 +59,7 @@ def __init__(self, name, channels=None, trigger_type='default'): self._trigger_times = None self._trigger_type = trigger_type self._triggered_channels = [] + self._pre_trigger_times = None def has_triggered(self): """ @@ -119,6 +120,39 @@ def get_triggered_channels(self): def set_triggered_channels(self, triggered_channels): self._triggered_channels = triggered_channels + def set_pre_trigger_times(self, pre_trigger_times): + """ + Set the pre-trigger times + + This parameter should only be set if this trigger + determines the readout windows (e.g. by :py:`NuRadioReco.modules.triggerTimeAdjuster`) + + Parameters + ---------- + pre_trigger_times: dict + keys are the channel_ids, and the value is the pre_trigger_time between the + start of the trace and the trigger time. + """ + self._pre_trigger_times = pre_trigger_times + + def get_pre_trigger_times(self): + """ + Return the pre_trigger_time between the start of the trace and the (global) trigger time + + If this trigger has not been used to adjust the readout windows, returns None instead + + Returns + ------- + pre_trigger_times: dict | None + If this trigger has been used to set the readout windows, returns a + dictionary where the keys are the channel ids and the values are the + time between the start of the channel trace and the trigger time. Otherwise, + returns None + + """ + + return self._pre_trigger_times + def serialize(self): return pickle.dumps(self.__dict__, protocol=4) From ee80152df237f62f2a800bc9d30fc9d338cde8a4 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Wed, 4 Oct 2023 11:51:35 +0200 Subject: [PATCH 15/54] save pre_trigger_times in Trigger object instead of adjusting trace_start_time --- NuRadioReco/modules/triggerTimeAdjuster.py | 193 +++++++++++++-------- 1 file changed, 116 insertions(+), 77 deletions(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index 002b30eb5..fc2a79a57 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -44,83 +44,122 @@ def begin(self, trigger_name=None, pre_trigger_time=55. * units.ns): self.__pre_trigger_time = pre_trigger_time @register_run() - def run(self, event, station, detector): - trigger = None - if self.__trigger_name is not None: - trigger = station.get_trigger(self.__trigger_name) - else: - min_trigger_time = None - for trig in station.get_triggers().values(): - if(trig.has_triggered()): - if min_trigger_time is None or trig.get_trigger_time() < min_trigger_time: - min_trigger_time = trig.get_trigger_time() - trigger = trig - if(min_trigger_time is not None): - logger.info(f"minimum trigger time is {min_trigger_time/units.ns:.2f}ns") - if trigger is None: - logger.info('No trigger found! Channel timings will not be changed.') - return - if trigger.has_triggered(): - trigger_time = trigger.get_trigger_time() - for channel in station.iter_channels(): - trigger_time_channel = trigger_time - channel.get_trace_start_time() + def run(self, event, station, detector, mode='sim_to_data'): + """ + Run the trigger time adjuster. + + This module can be used either to 'cut' the simulated traces into + the appropriate readout windows, or to adjust the trace start times + of simulated / real data to account for the different trigger readout + delays. + + Parameters + ---------- + event: NuRadioReco.framework.event.Event + + station: NuRadioReco.framework.base_station.Station + + detector: NuRadioReco.detector.detector.Detector + + mode: 'sim_to_data' (default) | 'data_to_sim' + If 'sim_to_data', cuts the (arbitrary-length) simulated traces + to the appropriate readout windows. If 'data_to_sim', + looks through all triggers in the station and adjusts the + trace_start_time according to the different readout delays + + """ + if mode == 'sim_to_data': + trigger = None + if self.__trigger_name is not None: + trigger = station.get_trigger(self.__trigger_name) + else: + min_trigger_time = None + for trig in station.get_triggers().values(): + if(trig.has_triggered()): + if min_trigger_time is None or trig.get_trigger_time() < min_trigger_time: + min_trigger_time = trig.get_trigger_time() + trigger = trig + if(min_trigger_time is not None): + logger.info(f"minimum trigger time is {min_trigger_time/units.ns:.2f}ns") + if trigger is None: + logger.info('No trigger found! Channel timings will not be changed.') + return + if trigger.has_triggered(): + trigger_time = trigger.get_trigger_time() + store_pre_trigger_time = {} # we also want to save the used pre_trigger_time + for channel in station.iter_channels(): + trigger_time_channel = trigger_time - channel.get_trace_start_time() - trace = channel.get_trace() - trace_length = len(trace) - number_of_samples = int( - 2 * np.ceil( # this should ensure that 1) the number of samples is even and 2) resampling to the detector sampling rate results in the correct number of samples (note that 2) can only be guaranteed if the detector sampling rate is lower than the current sampling rate) - detector.get_number_of_samples(station.get_id(), channel.get_id()) / 2 - * channel.get_sampling_rate() / detector.get_sampling_frequency(station.get_id(), channel.get_id()) - )) - if number_of_samples > trace.shape[0]: - logger.error("Input has fewer samples than desired output. Channels has only {} samples but {} samples are requested.".format( - trace.shape[0], number_of_samples)) - raise AttributeError - else: - sampling_rate = channel.get_sampling_rate() - trigger_time_sample = int(np.round(trigger_time_channel * sampling_rate)) - # logger.debug(f"channel {channel.get_id()}: trace_start_time = {channel.get_trace_start_time():.1f}ns, trigger time channel {trigger_time_channel/units.ns:.1f}ns, trigger time sample = {trigger_time_sample}") - pre_trigger_time = self.__pre_trigger_time - channel_id = channel.get_id() - trigger_name = trigger.get_name() - while isinstance(pre_trigger_time, dict): - if trigger_name in pre_trigger_time.keys(): # keys are different triggers - pre_trigger_time = pre_trigger_time[trigger_name] - elif channel_id in pre_trigger_time: # keys are channel_ids - pre_trigger_time = pre_trigger_time[channel_id] - else: - logger.error( - 'pre_trigger_time was specified as a dictionary, ' - f'but the neither the trigger_name {trigger_name} ' - f'nor the channel id {channel_id} are present as keys' - ) - raise KeyError - samples_before_trigger = int(pre_trigger_time * sampling_rate) - rel_station_time_samples = 0 - cut_samples_beginning = 0 - if(samples_before_trigger < trigger_time_sample): - cut_samples_beginning = trigger_time_sample - samples_before_trigger - roll_by = 0 - if(cut_samples_beginning + number_of_samples > trace_length): - logger.info("trigger time is sample {} but total trace length is only {} samples (requested trace length is {} with an offest of {} before trigger). To achieve desired configuration, trace will be rolled".format( - trigger_time_sample, trace_length, number_of_samples, samples_before_trigger)) - roll_by = cut_samples_beginning + number_of_samples - trace_length # roll_by is positive - trace = np.roll(trace, -1 * roll_by) - cut_samples_beginning -= roll_by - rel_station_time_samples = cut_samples_beginning + roll_by - elif(samples_before_trigger > trigger_time_sample): - roll_by = -trigger_time_sample + samples_before_trigger - logger.info( - "trigger time is before 'trigger offset window', the trace needs to be rolled by {} samples first".format(roll_by)) - trace = np.roll(trace, roll_by) - trigger_time_sample -= roll_by - rel_station_time_samples = -roll_by + trace = channel.get_trace() + trace_length = len(trace) + number_of_samples = int( + 2 * np.ceil( # this should ensure that 1) the number of samples is even and 2) resampling to the detector sampling rate results in the correct number of samples (note that 2) can only be guaranteed if the detector sampling rate is lower than the current sampling rate) + detector.get_number_of_samples(station.get_id(), channel.get_id()) / 2 + * channel.get_sampling_rate() / detector.get_sampling_frequency(station.get_id(), channel.get_id()) + )) + if number_of_samples > trace.shape[0]: + logger.error("Input has fewer samples than desired output. Channels has only {} samples but {} samples are requested.".format( + trace.shape[0], number_of_samples)) + raise AttributeError + else: + sampling_rate = channel.get_sampling_rate() + trigger_time_sample = int(np.round(trigger_time_channel * sampling_rate)) + # logger.debug(f"channel {channel.get_id()}: trace_start_time = {channel.get_trace_start_time():.1f}ns, trigger time channel {trigger_time_channel/units.ns:.1f}ns, trigger time sample = {trigger_time_sample}") + pre_trigger_time = self.__pre_trigger_time + channel_id = channel.get_id() + trigger_name = trigger.get_name() + while isinstance(pre_trigger_time, dict): + if trigger_name in pre_trigger_time.keys(): # keys are different triggers + pre_trigger_time = pre_trigger_time[trigger_name] + elif channel_id in pre_trigger_time: # keys are channel_ids + pre_trigger_time = pre_trigger_time[channel_id] + else: + logger.error( + 'pre_trigger_time was specified as a dictionary, ' + f'but the neither the trigger_name {trigger_name} ' + f'nor the channel id {channel_id} are present as keys' + ) + raise KeyError + samples_before_trigger = int(pre_trigger_time * sampling_rate) + rel_station_time_samples = 0 + cut_samples_beginning = 0 + if(samples_before_trigger < trigger_time_sample): + cut_samples_beginning = trigger_time_sample - samples_before_trigger + roll_by = 0 + if(cut_samples_beginning + number_of_samples > trace_length): + logger.info("trigger time is sample {} but total trace length is only {} samples (requested trace length is {} with an offest of {} before trigger). To achieve desired configuration, trace will be rolled".format( + trigger_time_sample, trace_length, number_of_samples, samples_before_trigger)) + roll_by = cut_samples_beginning + number_of_samples - trace_length # roll_by is positive + trace = np.roll(trace, -1 * roll_by) + cut_samples_beginning -= roll_by + rel_station_time_samples = cut_samples_beginning + roll_by + elif(samples_before_trigger > trigger_time_sample): + roll_by = -trigger_time_sample + samples_before_trigger + logger.info( + "trigger time is before 'trigger offset window', the trace needs to be rolled by {} samples first".format(roll_by)) + trace = np.roll(trace, roll_by) + trigger_time_sample -= roll_by + rel_station_time_samples = -roll_by - # shift trace to be in the correct location for cutting - # logger.debug(f"cutting trace to {cut_samples_beginning}-{number_of_samples + cut_samples_beginning} samples") - trace = trace[cut_samples_beginning:(number_of_samples + cut_samples_beginning)] - channel.set_trace(trace, channel.get_sampling_rate()) - channel.set_trace_start_time(channel.get_trace_start_time() + rel_station_time_samples / channel.get_sampling_rate()) - # logger.debug(f"setting trace start time to {channel.get_trace_start_time() + rel_station_time_samples / channel.get_sampling_rate():.0f} = {channel.get_trace_start_time():.0f} + {rel_station_time_samples / channel.get_sampling_rate():.0f}") + # shift trace to be in the correct location for cutting + # logger.debug(f"cutting trace to {cut_samples_beginning}-{number_of_samples + cut_samples_beginning} samples") + trace = trace[cut_samples_beginning:(number_of_samples + cut_samples_beginning)] + channel.set_trace(trace, channel.get_sampling_rate()) + channel.set_trace_start_time(trigger_time) + store_pre_trigger_time[channel_id] = pre_trigger_time + # channel.set_trace_start_time(channel.get_trace_start_time() + rel_station_time_samples / channel.get_sampling_rate()) + # logger.debug(f"setting trace start time to {channel.get_trace_start_time() + rel_station_time_samples / channel.get_sampling_rate():.0f} = {channel.get_trace_start_time():.0f} + {rel_station_time_samples / channel.get_sampling_rate():.0f}") + + # store the used pre_trigger_times + trigger.set_pre_trigger_times(store_pre_trigger_time) + + else: + logger.debug('Trigger {} has not triggered. Channel timings will not be changed.'.format(self.__trigger_name)) + elif mode == 'data_to_sim': + for trigger in station.get_triggers().values(): + pre_trigger_time = trigger.get_pre_trigger_times() + if pre_trigger_time is not None: + for channel in station.iter_channels(): + channel.set_trace_start_time(channel.get_trace_start_time()-pre_trigger_time[channel.get_id()]) else: - logger.debug('Trigger {} has not triggered. Channel timings will not be changed.'.format(self.__trigger_name)) + raise ValueError(f"Argument '{mode}' for mode is not valid. Options are 'sim_to_data' or 'data_to_sim'.") From b1bc9303da649c8cddd020eac06fda8b4aeed05f Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Mon, 23 Oct 2023 11:28:43 +0000 Subject: [PATCH 16/54] improve plotting --- NuRadioMC/SignalProp/examples/E02ToAir.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index a8a54f633..2c509c4df 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -33,7 +33,7 @@ ax.plot(x1[0], x1[2], 'ko') for i, x in enumerate([x2, x3, x4, x5]): print('finding solutions for ', x) - r = ray.ray_tracing(ice, log_level=logging.DEBUG, use_cpp=False) + r = ray.ray_tracing(ice, log_level=logging.WARNING, use_cpp=False) r.set_start_and_end_point(x1, x) r.find_solutions() if(r.has_solution()): @@ -50,6 +50,10 @@ receive_vectors[i, iS] = receive_vector zenith, azimuth = hp.cartesian_to_spherical(*receive_vector) print(" Receiving Zenith %.3f and Azimuth %.3f " % (zenith / units.deg, azimuth / units.deg)) + + # get focussing factor + focusing = r.get_focusing(0) + print(f" focusing factor = {focusing:.8f}") att = r.get_attenuation(iS, np.array([100, 200]) * units.MHz) print(att) @@ -65,7 +69,7 @@ x2_2d = np.array([X2r[0], X2r[2]]) r_2d = ray.ray_tracing_2D(ice) yy, zz = r_2d.get_path(x1_2d, x2_2d, ray_tracing_C0[i, iS]) - ax.plot(yy, zz, '{}'.format(php.get_color_linestyle(i)), label='{} C0 = {:.4f}'.format(ray_tracing_solution_type[i, iS], ray_tracing_C0[i, iS])) + ax.plot(yy, zz, '{}'.format(php.get_color_linestyle(i)), label='{} C0 = {:.4f}, f = {:.2f}'.format(ray_tracing_solution_type[i, iS], ray_tracing_C0[i, iS], focusing)) ax.plot(x2_2d[0], x2_2d[1], '{}{}-'.format('d', php.get_color(i))) ax.legend() From 4a20ccf560d41f6cf50976d395a3627191e1d365 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 27 Oct 2023 09:37:02 -0500 Subject: [PATCH 17/54] Adds RNO-G detector description based on 2023 survey campaign --- .../detector/RNO_G/RNO_season_2023.json | 3371 +++++++++++++++++ 1 file changed, 3371 insertions(+) create mode 100644 NuRadioReco/detector/RNO_G/RNO_season_2023.json diff --git a/NuRadioReco/detector/RNO_G/RNO_season_2023.json b/NuRadioReco/detector/RNO_G/RNO_season_2023.json new file mode 100644 index 000000000..b1be43208 --- /dev/null +++ b/NuRadioReco/detector/RNO_G/RNO_season_2023.json @@ -0,0 +1,3371 @@ +{ + "channels": { + "0": { + "station_id": 11, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -96.215, + "amp_type": "iglu", + "cab_time_delay": 716.2603798064285, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "1": { + "station_id": 11, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -95.174, + "amp_type": "iglu", + "cab_time_delay": 711.7615958304959, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "2": { + "station_id": 11, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.183, + "amp_type": "iglu", + "cab_time_delay": 706.495904921158, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "3": { + "station_id": 11, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.155, + "amp_type": "iglu", + "cab_time_delay": 702.195731800799, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "4": { + "station_id": 11, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.218, + "amp_type": "iglu", + "cab_time_delay": 690.6185223242599, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "5": { + "station_id": 11, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -79.121, + "amp_type": "iglu", + "cab_time_delay": 583.0629875035971, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "6": { + "station_id": 11, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -59.131, + "amp_type": "iglu", + "cab_time_delay": 484.257805977493, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "7": { + "station_id": 11, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -39.357, + "amp_type": "iglu", + "cab_time_delay": 387.1159239346471, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "8": { + "station_id": 11, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.177, + "amp_type": "iglu", + "cab_time_delay": 695.0077691915028, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "9": { + "station_id": 11, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 18.018524515656964, + "ant_position_y": -30.46999302062511, + "ant_position_z": -82.95, + "amp_type": "iglu", + "cab_time_delay": 704.8660580862335, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "10": { + "station_id": 11, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 18.018524515656964, + "ant_position_y": -30.46999302062511, + "ant_position_z": -81.94, + "amp_type": "iglu", + "cab_time_delay": 700.3063612901005, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "11": { + "station_id": 11, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 18.018524515656964, + "ant_position_y": -30.46999302062511, + "ant_position_z": -80.94, + "amp_type": "iglu", + "cab_time_delay": 695.125480287199, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "12": { + "station_id": 11, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 34.96881000487633, + "ant_position_y": -0.19141220844210238, + "ant_position_z": -94.66, + "amp_type": "iglu", + "cab_time_delay": 695.0278958661603, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "13": { + "station_id": 11, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 34.96881000487633, + "ant_position_y": -0.19141220844210238, + "ant_position_z": -95.66, + "amp_type": "iglu", + "cab_time_delay": 700.4737404863125, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "14": { + "station_id": 11, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 34.96881000487633, + "ant_position_y": -0.19141220844210238, + "ant_position_z": -96.67, + "amp_type": "iglu", + "cab_time_delay": 704.7261333365847, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "15": { + "station_id": 11, + "channel_id": 20, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 21.186104256390763, + "ant_position_y": -17.507627395985082, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.32699419041931, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "16": { + "station_id": 11, + "channel_id": 19, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 18.861426350457805, + "ant_position_y": -17.407802956481987, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.43439538521371, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "17": { + "station_id": 11, + "channel_id": 18, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 180.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 15.811921687833092, + "ant_position_y": -17.356092695615303, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.51563909722448, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "18": { + "station_id": 11, + "channel_id": 12, + "ant_rotation_phi": 243.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 333.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 25.245374429777485, + "ant_position_y": -9.271898202581497, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.556691492869795, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "19": { + "station_id": 11, + "channel_id": 13, + "ant_rotation_phi": 243.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 24.478617781898492, + "ant_position_y": -6.758508805503766, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.883012205651234, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "20": { + "station_id": 11, + "channel_id": 14, + "ant_rotation_phi": 243.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 153.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 23.709240577868513, + "ant_position_y": -4.359449327425068, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.20101294697667, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "21": { + "station_id": 11, + "channel_id": 15, + "ant_rotation_phi": 351.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 81.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 13.659226614984846, + "ant_position_y": -4.116966752974577, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.57621291824783, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "22": { + "station_id": 11, + "channel_id": 16, + "ant_rotation_phi": 351.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 12.579191943719934, + "ant_position_y": -6.338409142274372, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.398781691972694, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "23": { + "station_id": 11, + "channel_id": 17, + "ant_rotation_phi": 351.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 261.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 11.212162270289355, + "ant_position_y": -9.197601282929554, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.51394131174265, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "24": { + "station_id": 12, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.169786577567038, + "ant_position_y": 30.105416502163507, + "ant_position_z": -93.472, + "amp_type": "iglu", + "cab_time_delay": 704.8326985978704, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "25": { + "station_id": 12, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.169786577567038, + "ant_position_y": 30.105416502163507, + "ant_position_z": -92.507, + "amp_type": "iglu", + "cab_time_delay": 700.5853011208408, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "26": { + "station_id": 12, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.169786577567038, + "ant_position_y": 30.105416502163507, + "ant_position_z": -91.491, + "amp_type": "iglu", + "cab_time_delay": 695.0992381351823, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "27": { + "station_id": 12, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.54306172678298, + "ant_position_y": 30.752842104373485, + "ant_position_z": -92.799, + "amp_type": "iglu", + "cab_time_delay": 694.4016530026131, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "28": { + "station_id": 12, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.54306172678298, + "ant_position_y": 30.752842104373485, + "ant_position_z": -93.764, + "amp_type": "iglu", + "cab_time_delay": 699.8409090956202, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "29": { + "station_id": 12, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.54306172678298, + "ant_position_y": 30.752842104373485, + "ant_position_z": -94.755, + "amp_type": "iglu", + "cab_time_delay": 704.1016818511831, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "30": { + "station_id": 12, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.209, + "amp_type": "iglu", + "cab_time_delay": 717.8528684487527, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "31": { + "station_id": 12, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.243, + "amp_type": "iglu", + "cab_time_delay": 711.768, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "32": { + "station_id": 12, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.24, + "amp_type": "iglu", + "cab_time_delay": 707.7227809582946, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "33": { + "station_id": 12, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.262, + "amp_type": "iglu", + "cab_time_delay": 703.6051877256091, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "34": { + "station_id": 12, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -89.319, + "amp_type": "iglu", + "cab_time_delay": 691.703621620457, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "35": { + "station_id": 12, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -77.279, + "amp_type": "iglu", + "cab_time_delay": 583.1145481356763, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "36": { + "station_id": 12, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -57.252, + "amp_type": "iglu", + "cab_time_delay": 484.39813876384204, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "37": { + "station_id": 12, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -37.262, + "amp_type": "iglu", + "cab_time_delay": 387.28183644102774, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "38": { + "station_id": 12, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -90.297, + "amp_type": "iglu", + "cab_time_delay": 696.568527555139, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "39": { + "station_id": 12, + "channel_id": 17, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 240.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -7.25335276193141, + "ant_position_y": 21.34712319053824, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.91827499385921, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "40": { + "station_id": 12, + "channel_id": 16, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -6.003013655671339, + "ant_position_y": 23.847734045334846, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.83638105867064, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "41": { + "station_id": 12, + "channel_id": 15, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 60.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -4.360019878962476, + "ant_position_y": 26.51315499704583, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.730803410479524, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "42": { + "station_id": 12, + "channel_id": 14, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 120.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 4.522656115828795, + "ant_position_y": 25.910670151475188, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 46.01978301616729, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "43": { + "station_id": 12, + "channel_id": 13, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 5.838854205594316, + "ant_position_y": 24.5431206774806, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.742412236972015, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "44": { + "station_id": 12, + "channel_id": 12, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 300.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 7.03900825822484, + "ant_position_y": 21.618757784814306, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.839198595315175, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "45": { + "station_id": 12, + "channel_id": 18, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 180.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -3.172374694597238, + "ant_position_y": 13.365198347965134, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.59430891887436, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "46": { + "station_id": 12, + "channel_id": 19, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -0.09184631579910274, + "ant_position_y": 13.105892618229063, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.846178367177174, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "47": { + "station_id": 12, + "channel_id": 20, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 2.89471342104207, + "ant_position_y": 12.980386719307944, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.89728312662527, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-16T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "48": { + "station_id": 13, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -96.266, + "amp_type": "iglu", + "cab_time_delay": 716.5569003542323, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "49": { + "station_id": 13, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -95.25, + "amp_type": "iglu", + "cab_time_delay": 711.893994185995, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "50": { + "station_id": 13, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.259, + "amp_type": "iglu", + "cab_time_delay": 706.675717791364, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "51": { + "station_id": 13, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.218, + "amp_type": "iglu", + "cab_time_delay": 702.2727756955887, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "52": { + "station_id": 13, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.211, + "amp_type": "iglu", + "cab_time_delay": 690.6134090862433, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "53": { + "station_id": 13, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -79.175, + "amp_type": "iglu", + "cab_time_delay": 583.6510751776831, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "54": { + "station_id": 13, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -59.182, + "amp_type": "iglu", + "cab_time_delay": 484.71366908125884, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "55": { + "station_id": 13, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -39.243, + "amp_type": "iglu", + "cab_time_delay": 387.4357117300662, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "56": { + "station_id": 13, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.215, + "amp_type": "iglu", + "cab_time_delay": 695.082404371683, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "57": { + "station_id": 13, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.51197811169982, + "ant_position_y": 29.742022928890492, + "ant_position_z": -94.818, + "amp_type": "iglu", + "cab_time_delay": 705.8226166224723, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "58": { + "station_id": 13, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.51197811169982, + "ant_position_y": 29.742022928890492, + "ant_position_z": -93.802, + "amp_type": "iglu", + "cab_time_delay": 701.4365946711572, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "59": { + "station_id": 13, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.51197811169982, + "ant_position_y": 29.742022928890492, + "ant_position_z": -92.812, + "amp_type": "iglu", + "cab_time_delay": 696.2095237776895, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "60": { + "station_id": 13, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.95447838550831, + "ant_position_y": 30.12568265560958, + "ant_position_z": -93.091, + "amp_type": "iglu", + "cab_time_delay": 694.966307648661, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "61": { + "station_id": 13, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.95447838550831, + "ant_position_y": 30.12568265560958, + "ant_position_z": -94.082, + "amp_type": "iglu", + "cab_time_delay": 700.4557991679768, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "62": { + "station_id": 13, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.95447838550831, + "ant_position_y": 30.12568265560958, + "ant_position_z": -95.098, + "amp_type": "iglu", + "cab_time_delay": 704.7490940670381, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "63": { + "station_id": 13, + "channel_id": 17, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 240.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -7.659842837283577, + "ant_position_y": 21.248748214991338, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.4816564960653, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "64": { + "station_id": 13, + "channel_id": 16, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -6.394915495773603, + "ant_position_y": 23.544812894404913, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.917833705247084, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "65": { + "station_id": 13, + "channel_id": 15, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 60.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -4.7293936275261785, + "ant_position_y": 25.843182149080803, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.38950686394398, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "66": { + "station_id": 13, + "channel_id": 14, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 120.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 4.387081260395007, + "ant_position_y": 25.783898894905633, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.977255276242445, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "67": { + "station_id": 13, + "channel_id": 13, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 5.591569317517724, + "ant_position_y": 23.363173077020747, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.85528602130243, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "68": { + "station_id": 13, + "channel_id": 12, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 300.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 6.975139683861471, + "ant_position_y": 21.094408011153973, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 46.01961725310455, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "69": { + "station_id": 13, + "channel_id": 20, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 2.4579457368663498, + "ant_position_y": 13.335390086505413, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.764002862920535, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "70": { + "station_id": 13, + "channel_id": 19, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -0.08945918504923611, + "ant_position_y": 13.066877906649552, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.97540552376522, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "71": { + "station_id": 13, + "channel_id": 18, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 180.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -2.8286647134991654, + "ant_position_y": 13.04946226926404, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.75640441890302, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-30T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "72": { + "station_id": 21, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -95.37, + "amp_type": "iglu", + "cab_time_delay": 716.2340516693513, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "73": { + "station_id": 21, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.37, + "amp_type": "iglu", + "cab_time_delay": 711.6738111253712, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "74": { + "station_id": 21, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.36, + "amp_type": "iglu", + "cab_time_delay": 706.3043868097873, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "75": { + "station_id": 21, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.36, + "amp_type": "iglu", + "cab_time_delay": 702.1201325816487, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "76": { + "station_id": 21, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -90.39, + "amp_type": "iglu", + "cab_time_delay": 689.9220583040554, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "77": { + "station_id": 21, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -78.42, + "amp_type": "iglu", + "cab_time_delay": 583.6085635800221, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "78": { + "station_id": 21, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -57.98, + "amp_type": "iglu", + "cab_time_delay": 484.73964774406437, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "79": { + "station_id": 21, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -38.33, + "amp_type": "iglu", + "cab_time_delay": 387.6103598042084, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "80": { + "station_id": 21, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.37, + "amp_type": "iglu", + "cab_time_delay": 695.0287325115062, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "81": { + "station_id": 21, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.237822409178648, + "ant_position_y": 29.436009810348025, + "ant_position_z": -94.7, + "amp_type": "iglu", + "cab_time_delay": 706.0498570594092, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "82": { + "station_id": 21, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.237822409178648, + "ant_position_y": 29.436009810348025, + "ant_position_z": -93.71, + "amp_type": "iglu", + "cab_time_delay": 701.7155539494165, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "83": { + "station_id": 21, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.237822409178648, + "ant_position_y": 29.436009810348025, + "ant_position_z": -92.7, + "amp_type": "iglu", + "cab_time_delay": 696.5371961764442, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "84": { + "station_id": 21, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.1288896622176, + "ant_position_y": -1.2407598474927681, + "ant_position_z": -93.37, + "amp_type": "iglu", + "cab_time_delay": 695.8319798499194, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "85": { + "station_id": 21, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.1288896622176, + "ant_position_y": -1.2407598474927681, + "ant_position_z": -94.34, + "amp_type": "iglu", + "cab_time_delay": 701.0882803491144, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "86": { + "station_id": 21, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.1288896622176, + "ant_position_y": -1.2407598474927681, + "ant_position_z": -95.37, + "amp_type": "iglu", + "cab_time_delay": 705.241651007666, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "87": { + "station_id": 21, + "channel_id": 15, + "ant_rotation_phi": 254.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 164.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -20.913616334864287, + "ant_position_y": 16.537611216532184, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.254956245215354, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "88": { + "station_id": 21, + "channel_id": 16, + "ant_rotation_phi": 254.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -18.326662967684683, + "ant_position_y": 16.91462123190064, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.82958226071196, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "89": { + "station_id": 21, + "channel_id": 17, + "ant_rotation_phi": 254.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 344.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -15.404148418065631, + "ant_position_y": 17.46318633982372, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 48.96724049428868, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "90": { + "station_id": 21, + "channel_id": 14, + "ant_rotation_phi": 65.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 155.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -25.02971666518448, + "ant_position_y": 7.293175091846081, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.42704773531392, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "91": { + "station_id": 21, + "channel_id": 13, + "ant_rotation_phi": 65.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -23.892386133465607, + "ant_position_y": 4.978986751043749, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.43375896297529, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "92": { + "station_id": 21, + "channel_id": 12, + "ant_rotation_phi": 65.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 335.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -22.479689741668324, + "ant_position_y": 2.80783833285102, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.36961127763919, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "93": { + "station_id": 21, + "channel_id": 20, + "ant_rotation_phi": 118.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 208.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -11.266588048738129, + "ant_position_y": 2.9011467712219314, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 48.99288182695537, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "94": { + "station_id": 21, + "channel_id": 19, + "ant_rotation_phi": 118.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -10.00827839620348, + "ant_position_y": 5.2833313663230115, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.75937993162733, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "95": { + "station_id": 21, + "channel_id": 18, + "ant_rotation_phi": 118.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 28.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -8.513397338379718, + "ant_position_y": 7.724537269470318, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.48968661430838, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "96": { + "station_id": 22, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.199498709672298, + "ant_position_y": 29.357402778427513, + "ant_position_z": -96.8, + "amp_type": "iglu", + "cab_time_delay": 706.0498570594092, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "97": { + "station_id": 22, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.199498709672298, + "ant_position_y": 29.357402778427513, + "ant_position_z": -95.8, + "amp_type": "iglu", + "cab_time_delay": 701.7155539494165, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "98": { + "station_id": 22, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.199498709672298, + "ant_position_y": 29.357402778427513, + "ant_position_z": -94.8, + "amp_type": "iglu", + "cab_time_delay": 696.5371961764442, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "99": { + "station_id": 22, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.39538465744613, + "ant_position_y": 29.513529144740687, + "ant_position_z": -94.61, + "amp_type": "iglu", + "cab_time_delay": 695.0887166749822, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "100": { + "station_id": 22, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.39538465744613, + "ant_position_y": 29.513529144740687, + "ant_position_z": -95.61, + "amp_type": "iglu", + "cab_time_delay": 700.5097259660157, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "101": { + "station_id": 22, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 16.39538465744613, + "ant_position_y": 29.513529144740687, + "ant_position_z": -96.62, + "amp_type": "iglu", + "cab_time_delay": 704.7852281608289, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "102": { + "station_id": 22, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -95.67, + "amp_type": "iglu", + "cab_time_delay": 716.5273297729007, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "103": { + "station_id": 22, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.66, + "amp_type": "iglu", + "cab_time_delay": 711.9338529670598, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "104": { + "station_id": 22, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.66, + "amp_type": "iglu", + "cab_time_delay": 706.6720443161013, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "105": { + "station_id": 22, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.66, + "amp_type": "iglu", + "cab_time_delay": 702.2200344958492, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "106": { + "station_id": 22, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -90.72, + "amp_type": "iglu", + "cab_time_delay": 690.5959592568203, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "107": { + "station_id": 22, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -78.7, + "amp_type": "iglu", + "cab_time_delay": 583.316058590185, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "108": { + "station_id": 22, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -58.62, + "amp_type": "iglu", + "cab_time_delay": 484.8636828671781, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "109": { + "station_id": 22, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -38.53, + "amp_type": "iglu", + "cab_time_delay": 387.5505598225923, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "110": { + "station_id": 22, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.67, + "amp_type": "iglu", + "cab_time_delay": 695.1562845004979, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "111": { + "station_id": 22, + "channel_id": 17, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 240.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -8.6049483702011, + "ant_position_y": 21.47786411167135, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.71319900390884, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "112": { + "station_id": 22, + "channel_id": 16, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -7.5612403179679575, + "ant_position_y": 23.893840401733087, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.602214424020616, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "113": { + "station_id": 22, + "channel_id": 15, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 60.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -6.378764994213384, + "ant_position_y": 26.059809190085844, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.983380707350655, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "114": { + "station_id": 22, + "channel_id": 13, + "ant_rotation_phi": 213.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 7.223712045816455, + "ant_position_y": 23.24066900706316, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.989096119098235, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "115": { + "station_id": 22, + "channel_id": 20, + "ant_rotation_phi": 81.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 351.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 2.5453667925172567, + "ant_position_y": 11.108734895638918, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.510627155343215, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "116": { + "station_id": 22, + "channel_id": 19, + "ant_rotation_phi": 81.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -0.033351822535991005, + "ant_position_y": 11.348087174853845, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.777650776480506, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "117": { + "station_id": 22, + "channel_id": 14, + "ant_rotation_phi": 213.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 123.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 5.363380066230661, + "ant_position_y": 25.697792916809476, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.527426912487186, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "118": { + "station_id": 22, + "channel_id": 12, + "ant_rotation_phi": 213.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 303.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 8.118574044456068, + "ant_position_y": 20.662247745052127, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 49.07793698494233, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "119": { + "station_id": 22, + "channel_id": 18, + "ant_rotation_phi": 81.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 171.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -2.5448638218912265, + "ant_position_y": 11.377546831037535, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 52.95363112541108, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "120": { + "station_id": 23, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.717, + "amp_type": "iglu", + "cab_time_delay": 717.0534637454359, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "121": { + "station_id": 23, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.751, + "amp_type": "iglu", + "cab_time_delay": 712.4004767769894, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "122": { + "station_id": 23, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.735, + "amp_type": "iglu", + "cab_time_delay": 707.1524946995909, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "123": { + "station_id": 23, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -91.745, + "amp_type": "iglu", + "cab_time_delay": 702.8617993250889, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "124": { + "station_id": 23, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -89.764, + "amp_type": "iglu", + "cab_time_delay": 690.9500500747815, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "125": { + "station_id": 23, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -77.699, + "amp_type": "iglu", + "cab_time_delay": 582.3601331742286, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "126": { + "station_id": 23, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -57.709, + "amp_type": "iglu", + "cab_time_delay": 483.6670431088573, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "127": { + "station_id": 23, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -37.719, + "amp_type": "iglu", + "cab_time_delay": 386.37548644736046, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "128": { + "station_id": 23, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -90.729, + "amp_type": "iglu", + "cab_time_delay": 695.8484064369925, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "129": { + "station_id": 23, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.54191609133999, + "ant_position_y": -0.1224676067567998, + "ant_position_z": -94.945, + "amp_type": "iglu", + "cab_time_delay": 705.2304586807497, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "130": { + "station_id": 23, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.54191609133999, + "ant_position_y": -0.1224676067567998, + "ant_position_z": -93.942, + "amp_type": "iglu", + "cab_time_delay": 701.1086074022868, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "131": { + "station_id": 23, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -34.54191609133999, + "ant_position_y": -0.1224676067567998, + "ant_position_z": -92.951, + "amp_type": "iglu", + "cab_time_delay": 695.7224638947405, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "132": { + "station_id": 23, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.11733208278625, + "ant_position_y": 29.755711625947697, + "ant_position_z": -93.764, + "amp_type": "iglu", + "cab_time_delay": 696.6159498314443, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "133": { + "station_id": 23, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.11733208278625, + "ant_position_y": 29.755711625947697, + "ant_position_z": -94.767, + "amp_type": "iglu", + "cab_time_delay": 701.7183093306504, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "134": { + "station_id": 23, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.11733208278625, + "ant_position_y": 29.755711625947697, + "ant_position_z": -95.783, + "amp_type": "iglu", + "cab_time_delay": 706.0542491285506, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "135": { + "station_id": 23, + "channel_id": 14, + "ant_rotation_phi": 270.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 180.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -19.211573683413533, + "ant_position_y": 17.319975661629087, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.54795118967973, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "136": { + "station_id": 23, + "channel_id": 13, + "ant_rotation_phi": 270.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -16.664335137387823, + "ant_position_y": 17.16331723660869, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.7577340623503, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "137": { + "station_id": 23, + "channel_id": 20, + "ant_rotation_phi": 150.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 60.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -8.850478318870572, + "ant_position_y": 9.054268302787932, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.9267504993418, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "138": { + "station_id": 23, + "channel_id": 19, + "ant_rotation_phi": 150.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -10.17433696300425, + "ant_position_y": 6.733546452867358, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.3209139461091, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "139": { + "station_id": 23, + "channel_id": 18, + "ant_rotation_phi": 150.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 240.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -11.704241496685498, + "ant_position_y": 4.103020992519305, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.77034957757189, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-23T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "140": { + "station_id": 23, + "channel_id": 17, + "ant_rotation_phi": 30.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 300.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -21.051474002000376, + "ant_position_y": 4.618674469174039, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.39048669843476, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "141": { + "station_id": 23, + "channel_id": 16, + "ant_rotation_phi": 30.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -22.374936914001708, + "ant_position_y": 6.947502613768393, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.40997699209948, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "142": { + "station_id": 23, + "channel_id": 15, + "ant_rotation_phi": 30.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 120.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -23.792431708424672, + "ant_position_y": 9.490450425534164, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.83094862959857, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "143": { + "station_id": 24, + "channel_id": 9, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.6294982705393, + "ant_position_y": 30.01858899282888, + "ant_position_z": -97.511, + "amp_type": "iglu", + "cab_time_delay": 705.32011112294, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "144": { + "station_id": 24, + "channel_id": 10, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.6294982705393, + "ant_position_y": 30.01858899282888, + "ant_position_z": -96.495, + "amp_type": "iglu", + "cab_time_delay": 701.118919397236, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "145": { + "station_id": 24, + "channel_id": 11, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -17.6294982705393, + "ant_position_y": 30.01858899282888, + "ant_position_z": -95.504, + "amp_type": "iglu", + "cab_time_delay": 695.8325149249811, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "146": { + "station_id": 24, + "channel_id": 0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -97.638, + "amp_type": "iglu", + "cab_time_delay": 716.7233780139192, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "147": { + "station_id": 24, + "channel_id": 1, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -96.622, + "amp_type": "iglu", + "cab_time_delay": 712.1044846158909, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "148": { + "station_id": 24, + "channel_id": 2, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -95.631, + "amp_type": "iglu", + "cab_time_delay": 706.8892411537641, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "149": { + "station_id": 24, + "channel_id": 3, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -94.615, + "amp_type": "iglu", + "cab_time_delay": 702.4226072909622, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "150": { + "station_id": 24, + "channel_id": 4, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -92.659, + "amp_type": "iglu", + "cab_time_delay": 690.761838353226, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "151": { + "station_id": 24, + "channel_id": 5, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -80.594, + "amp_type": "iglu", + "cab_time_delay": 583.7560433649974, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "152": { + "station_id": 24, + "channel_id": 6, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -60.604, + "amp_type": "iglu", + "cab_time_delay": 484.7189595813629, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "153": { + "station_id": 24, + "channel_id": 7, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -40.64, + "amp_type": "iglu", + "cab_time_delay": 387.69316314434445, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "154": { + "station_id": 24, + "channel_id": 8, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 0.0, + "ant_position_y": 0.0, + "ant_position_z": -93.65, + "amp_type": "iglu", + "cab_time_delay": 695.2098026860888, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "155": { + "station_id": 24, + "channel_id": 21, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.01617867674605, + "ant_position_y": 30.089051222671515, + "ant_position_z": -78.372, + "amp_type": "iglu", + "cab_time_delay": 696.0136180352841, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" + }, + "156": { + "station_id": 24, + "channel_id": 22, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.01617867674605, + "ant_position_y": 30.089051222671515, + "ant_position_z": -79.375, + "amp_type": "iglu", + "cab_time_delay": 701.1819591709564, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "157": { + "station_id": 24, + "channel_id": 23, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 17.01617867674605, + "ant_position_y": 30.089051222671515, + "ant_position_z": -80.391, + "amp_type": "iglu", + "cab_time_delay": 705.6007352061425, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "RNOG_vpol_4inch_center_n1.73" + }, + "158": { + "station_id": 24, + "channel_id": 15, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 60.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -5.171399705444173, + "ant_position_y": 25.841320159085626, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.75439206401613, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "159": { + "station_id": 24, + "channel_id": 16, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -6.320901644269725, + "ant_position_y": 23.59709970223321, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.458824371215705, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "160": { + "station_id": 24, + "channel_id": 17, + "ant_rotation_phi": 330.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 240.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -7.721578483910434, + "ant_position_y": 21.042051775049913, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.767816340568835, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "161": { + "station_id": 24, + "channel_id": 18, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 180.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -3.1296279810530905, + "ant_position_y": 13.016645591927954, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.72635561302779, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "162": { + "station_id": 24, + "channel_id": 19, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": -0.49897710181994626, + "ant_position_y": 12.8886429463073, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 46.20942421716986, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "163": { + "station_id": 24, + "channel_id": 20, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 2.4626351626473024, + "ant_position_y": 12.830654934149607, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.8043779968151, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-05T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "164": { + "station_id": 24, + "channel_id": 12, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 300.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 7.210917268336061, + "ant_position_y": 21.083718102880994, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.364441874451146, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "165": { + "station_id": 24, + "channel_id": 13, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_position_x": 5.667346182745405, + "ant_position_y": 23.556566756125903, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.824131294404665, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "166": { + "station_id": 24, + "channel_id": 14, + "ant_rotation_phi": 210.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 120.0, + "ant_orientation_theta": 120.0, + "ant_position_x": 4.16767475236702, + "ant_position_y": 25.935868936541738, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.61878753278558, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + } + }, + "stations": { + "11": { + "station_id": 11, + "pos_easting": -1572.8161736544128, + "pos_northing": 729.3722386485937, + "pos_altitude": -3.783231150073059, + "pos_site": "summit", + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "12": { + "station_id": 12, + "pos_easting": -1352.364325059356, + "pos_northing": 1911.1738485252934, + "pos_altitude": -4.03610097553792, + "pos_site": "summit", + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "13": { + "station_id": 13, + "pos_easting": -1147.4927545674973, + "pos_northing": 3124.0456946337963, + "pos_altitude": -4.50342952129995, + "pos_site": "summit", + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "21": { + "station_id": 21, + "pos_easting": -308.1768397289919, + "pos_northing": 495.8958356648789, + "pos_altitude": -3.001389475218261, + "pos_site": "summit", + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "22": { + "station_id": 22, + "pos_easting": -138.03400922620767, + "pos_northing": 1708.005557160627, + "pos_altitude": -2.463255858468301, + "pos_site": "summit", + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "23": { + "station_id": 23, + "pos_easting": 82.4071346964168, + "pos_northing": 2949.9982332582017, + "pos_altitude": -2.837141509331976, + "pos_site": "summit", + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + }, + "24": { + "station_id": 24, + "pos_easting": 269.08737268271943, + "pos_northing": 4133.055983521074, + "pos_altitude": -3.6948952982193837, + "pos_site": "summit", + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00" + } + }, + "devices": { + "1": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 18.018524515656964, + "ant_position_y": -30.46999302062511, + "ant_position_z": -79.92, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 11 + }, + "0": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 34.96881000487633, + "ant_position_y": -0.19141220844210238, + "ant_position_z": -93.68, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 11 + }, + "2": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 18.107832415371604, + "ant_position_y": -1.3719442312160481, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2021-07-11T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 11 + }, + "4": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.169786577567038, + "ant_position_y": 30.105416502163507, + "ant_position_z": -90.5, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 12 + }, + "3": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 17.54306172678298, + "ant_position_y": 30.752842104373485, + "ant_position_z": -91.821, + "commission_time": "{TinyDate}:2022-06-17T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 12 + }, + "5": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -0.3753118916176845, + "ant_position_y": 29.75475708622207, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2022-06-21T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 12 + }, + "7": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.51197811169982, + "ant_position_y": 29.742022928890492, + "ant_position_z": -91.834, + "commission_time": "{TinyDate}:2022-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 13 + }, + "6": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 16.95447838550831, + "ant_position_y": 30.12568265560958, + "ant_position_z": -92.1, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 13 + }, + "8": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -0.5740155279763712, + "ant_position_y": 35.923120880881925, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2022-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 13 + }, + "10": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.237822409178648, + "ant_position_y": 29.436009810348025, + "ant_position_z": -91.72, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 21 + }, + "9": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -34.1288896622176, + "ant_position_y": -1.2407598474927681, + "ant_position_z": -92.37, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 21 + }, + "11": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -51.74498926313953, + "ant_position_y": 29.063846909018764, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2021-07-04T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 21 + }, + "13": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.199498709672298, + "ant_position_y": 29.357402778427513, + "ant_position_z": -93.8, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 22 + }, + "12": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 16.39538465744613, + "ant_position_y": 29.513529144740687, + "ant_position_z": -93.62, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 22 + }, + "14": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -51.74498926313953, + "ant_position_y": 29.063846909018764, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2021-07-01T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 22 + }, + "16": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -34.54191609133999, + "ant_position_y": -0.1224676067567998, + "ant_position_z": -91.961, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 23 + }, + "15": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.11733208278625, + "ant_position_y": 29.755711625947697, + "ant_position_z": -92.774, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 23 + }, + "17": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -28.515812899579103, + "ant_position_y": 19.496516800697464, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2022-06-26T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 23 + }, + "19": { + "ant_comment": "Helper String B Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -17.6294982705393, + "ant_position_y": 30.01858899282888, + "ant_position_z": -94.513, + "commission_time": "{TinyDate}:2022-07-06T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 1, + "station_id": 24 + }, + "18": { + "ant_comment": "Helper String C Cal Vpol", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": 17.01617867674605, + "ant_position_y": 30.089051222671515, + "ant_position_z": -77.368, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 0, + "station_id": 24 + }, + "20": { + "ant_comment": "Surface Cal Pulser", + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 0.0, + "ant_rotation_phi": 90.0, + "ant_rotation_theta": 90.0, + "ant_position_x": -0.9999079284443724, + "ant_position_y": 36.36050283253098, + "ant_position_z": -0.5, + "commission_time": "{TinyDate}:2022-07-12T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "device_id": 2, + "station_id": 24 + } + } +} From a407250fdce3746dd089639ec49b8c14b3edc4a3 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Mon, 13 Nov 2023 15:59:26 +0100 Subject: [PATCH 18/54] update changelog --- changelog.txt | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/changelog.txt b/changelog.txt index 9a44a4779..7c2b35083 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,7 +4,7 @@ please update the categories "new features" and "bugfixes" before a pull request version 2.2.0-dev new features: -- expand values stored in SimplePhasedTrigger +- expand values stored in SimplePhasedTrigger - added getting to query ARZ charge-excess profiles - upgrade to proposal v 7.5.0 with new NuRadioProposal interface and improved LPM treatment - add script to generate / download pre-calculated proposal tables for known detector configurations @@ -14,6 +14,9 @@ new features: - added ability to generate high-low-triggered noise on a narrow band but return full-band waveforms - phased array noise generation utility class: calculate trigger time and cut trace accordingly - use Philox noise generator in noise adder module (this changes the default random sequence) +- triggerTimeAdjuster now works more reliably and supports different readout windows per channel. +The readout time adjustment now is stored in the trigger object and needs to be accounted for in analysis +by running the triggerTimeAdjuster, analogously to the channelAddCableDelay module. bugfixes: - fixed/improved C++ raytracer not finding solutions for some near-horizontal or near-shadowzone vertices @@ -41,13 +44,13 @@ is not found in the current station version 2.1.6 bugfixes: -- the n_interaction parameter was accidentally overridden. n_interaction counts the number of interactions of taus and +- the n_interaction parameter was accidentally overridden. n_interaction counts the number of interactions of taus and muons generated in the corresponding CC interactions. The bug resulted in the following behaviour: The n_interaction value of the primary neutrino interaction was overridden with the n_interaction value of the last shower of the previous event group. If the previous event group was a CC interaction, the n_interaction value of the primary interaction was set to a value larger than 1. If that happened, and if the primary -shower did not trigger the detector, this shower is not added to the output hdf5 file. This has consquences for analyzing hdf5 files for -secondary interactions. With this bug, the initial interaction needs to be idenfified with np.abs(flavor) == 14 or 16. -This bug does not effect any effective volume calculation. +shower did not trigger the detector, this shower is not added to the output hdf5 file. This has consquences for analyzing hdf5 files for +secondary interactions. With this bug, the initial interaction needs to be idenfified with np.abs(flavor) == 14 or 16. +This bug does not effect any effective volume calculation. new features: - add hvsp1, update idl1 model for emitter simulation (from KU lab measurements) and remove hvsp2(no longer in use for experiment) @@ -57,7 +60,7 @@ version 2.1.5 bugfixes: - the phased array trigger module did not calculate the trigger time. - The trigger time was always set to the trace start time. This got fixed in this version. + The trigger time was always set to the trace start time. This got fixed in this version. version 2.1.4 @@ -70,7 +73,7 @@ bugfixes: version 2.1.2 bugfixes: -- Fixes that the generic detector crashes for certain detector files. +- Fixes that the generic detector crashes for certain detector files. version 2.1.1 new features: @@ -87,7 +90,7 @@ new features: - add a basic uproot data reader for RNO-G data - add option to simulate emitters - added helper function for cosmic ray flux models -- speed improvements of ARZ model through use of numba +- speed improvements of ARZ model through use of numba bugfixes: - correct volume extension depending on zenith angle range when running with @@ -106,26 +109,26 @@ bugfixes: version 2.0.0 - NuRadioReco is merged into the NuRadioMC repository. No new features were added and everything works (e.g. the imports) -as before but the NuRadioReco code is now part of the NuRadioMC github repository to simplify code development where NuRadioMC -changes depend on changes in NuRadioReco. +as before but the NuRadioReco code is now part of the NuRadioMC github repository to simplify code development where NuRadioMC +changes depend on changes in NuRadioReco. version 1.2.0 new features: -- major change in internal looping. Now, the radio signal from each shower is calculated and signal arrival times +- major change in internal looping. Now, the radio signal from each shower is calculated and signal arrival times (from different shower, ray racing solutions and receiver positions) that cluster together are simualted as one event - merge hdf5 utility has multithreading option (and was refactored -> function names changed) - distance cut can sum up all sourounding shower energies to properly account for possible interference - noise temperature can be specified per station and channel - trigger thresholds can be specified per channel -- bandwidth can be specified per station and channel +- bandwidth can be specified per station and channel - specifying the detector simulation became easier (check out the updated examples) - memory consumption was optimized to stay <4GB per core - random realization of showers are saved so that triggered events can be resimulated using the same random realization - added option for noiseless channels in a "with noise" simulation - add option to generate events on the fly and pass them directly to the simulation part (no need to save input hdf5 files anymore) - added uncertainties to CTW cross sections -- +- bugfixes: - Fixed issue with merge hdf5 utility so that "event_group_ids" are properly unique @@ -133,7 +136,7 @@ bugfixes: -version 1.1.2 - +version 1.1.2 - new features: - Veff utility can now handle extended bins - New tutorial and example for the webinar @@ -150,10 +153,10 @@ bugfixes: version 1.1.1 - 2020/03/23 new features -- New version for the ARZ model available (ARZ2020) -- a list with event ids can be passed to the main simulation class. All events not in this list will not be simulated. - This is useful for a quick resimulation of certain events. -- Alvarez???? Askaryan models now place the trace into the center of the trace (instead of 50ns into the trace) +- New version for the ARZ model available (ARZ2020) +- a list with event ids can be passed to the main simulation class. All events not in this list will not be simulated. + This is useful for a quick resimulation of certain events. +- Alvarez???? Askaryan models now place the trace into the center of the trace (instead of 50ns into the trace) - New data set array 'vertex_times' contains the time elapsed from the first interaction to the current interaction - new utility to split output hdf5 files into smaller chucks (to be able to resimulate events on a cluster) - Greenland added to proposal config @@ -161,11 +164,11 @@ new features - improved Earth model (PREM), path from interaction vertex through Earth is calculated (before interaction was assumed to happen at the surface) - detector description is saved to nur output files -- new handling of random numbers. Each module has its own random generator instance. Global seed can be controlled via +- new handling of random numbers. Each module has its own random generator instance. Global seed can be controlled via config file setting. bugfixes: -- ARZxxxx and Alvarez2009 Askaryan modules now use the same (random) shower per event. +- ARZxxxx and Alvarez2009 Askaryan modules now use the same (random) shower per event. - fixes zenith distribution of event generator back to cos(zenith) - ray tracing precision was increased to 1e-9 - saveguard against too many ray tracing solutions added @@ -189,9 +192,9 @@ bugfixes: - fixed that Veff utility script didn't calculate effective volumes for individual triggers - fixed that the multiple_triggers key was not created for multiple stations - Angular distribution now accounts for projected area - - - - + - + + version 1.0.0 - 2019/08/30 - first python 3 release From 3b35d00a586b679085fcee26f71a084936244065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Wed, 22 Nov 2023 17:13:00 +0100 Subject: [PATCH 19/54] Fix calculation of bandwidth --- NuRadioMC/simulation/simulation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 42a1d19fa..ce0556b05 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -312,9 +312,11 @@ def __init__(self, inputfilename, filt *= instance.get_filter(ff, self._station_id, channel_id, self._det, **kwargs) self._amplification_per_channel[self._station_id][channel_id] = np.abs(filt).max() - bandwidth = np.trapz(np.abs(filt) ** 2, ff) + in_bandwith_lim = self._amplification_per_channel[self._station_id][channel_id] / 100 # a factor of 100 corresponds to -40 dB in amplitude + ff_in_bandwith = ff[np.abs(filt) > in_bandwith_lim] + bandwidth = ff_in_bandwith[-1] - ff_in_bandwith[0] self._bandwidth_per_channel[self._station_id][channel_id] = bandwidth - logger.status(f"bandwidth of station {self._station_id} channel {channel_id} is {bandwidth/units.MHz:.1f}MHz") + logger.status(f"bandwidth of station {self._station_id} channel {channel_id} is {bandwidth / units.MHz:.1f} MHz") ################################ @@ -851,7 +853,7 @@ def run(self): self._station = NuRadioReco.framework.station.Station(self._station_id) self._station.set_sim_station(self._sim_station) self._station.get_sim_station().set_station_time(self._evt_time) - + # convert efields to voltages at digitizer if hasattr(self, '_detector_simulation_part1'): # we give the user the opportunity to define a custom detector simulation From 5d0507fd93076dce93d8cb4372642db90872352f Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 24 Nov 2023 14:08:12 +0000 Subject: [PATCH 20/54] add warning that tranmission coefficient is not calculated for ice/air ray tracing --- NuRadioMC/SignalProp/analyticraytracing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 436d6fcd6..41f5cd9eb 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -685,6 +685,7 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, z_turn = 0 y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) x2 = [y_turn, z_turn] + self.__logger.warning(f"transmission coefficient of propagation from ice into air, or air into ice is not taken into account in attenuation calculation!") if self.use_cpp: mask = frequency > 0 From cbf0feb34f6bc7ec549a3e7ba49a9c8f5346762a Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Mon, 27 Nov 2023 13:38:55 +0000 Subject: [PATCH 21/54] add calculation of transmission coefficients --- NuRadioMC/SignalProp/analyticraytracing.py | 44 +++++++++++++------ NuRadioMC/SignalProp/examples/E02ToAir.py | 51 +++++++++++++--------- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 41f5cd9eb..c86621a07 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -2309,19 +2309,37 @@ def apply_propagation_effects(self, efield, i_solution): for zenith_reflection in zenith_reflections: # loop through all possible reflections if (zenith_reflection is None): # skip all ray segments where not reflection at surface happens continue - r_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_r_p( - zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) - r_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_r_s( - zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) - efield[efp.reflection_coefficient_theta] = r_theta - efield[efp.reflection_coefficient_phi] = r_phi - - spec[1] *= r_theta - spec[2] *= r_phi - self.__logger.debug( - "ray hits the surface at an angle {:.2f}deg -> reflection coefficient is r_theta = {:.2f}, r_phi = {:.2f}".format( - zenith_reflection / units.deg, - r_theta, r_phi)) + if(self._x2[1] > 0): # we need to treat the case of air to ice/ice to air propagation sepatately: + # air/ice propagation + if(not self._swap): # ice to air case + t_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_p( + zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + t_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_s( + zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + self.__logger.warning(f"propagating from ice to air: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") + else: # air to ice + t_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_p( + zenith_reflection, n_1=1., n_2=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + t_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_s( + zenith_reflection, n_1=1., n_2=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + self.__logger.warning(f"propagating from air to ice: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") + spec[1] *= t_theta + spec[2] *= t_phi + else: + #in-ice propagation + r_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_r_p( + zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + r_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_r_s( + zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) + efield[efp.reflection_coefficient_theta] = r_theta + efield[efp.reflection_coefficient_phi] = r_phi + + spec[1] *= r_theta + spec[2] *= r_phi + self.__logger.warning( + "ray hits the surface at an angle {:.2f}deg -> reflection coefficient is r_theta = {:.2f}, r_phi = {:.2f}".format( + zenith_reflection / units.deg, + r_theta, r_phi)) i_reflections = self.get_results()[i_solution]['reflection'] if (i_reflections > 0): # take into account possible bottom reflections # each reflection lowers the amplitude by the reflection coefficient and introduces a phase shift diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index 2c509c4df..1ef30857f 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -4,6 +4,7 @@ from NuRadioMC.SignalProp import analyticraytracing as ray from NuRadioReco.utilities import units from NuRadioMC.utilities import medium +import NuRadioReco.framework.electric_field import logging from radiotools import helper as hp from radiotools import plthelpers as php @@ -12,11 +13,15 @@ # ray.cpp_available=False x1 = np.array([0, 0., -149.]) * units.m + x2 = np.array([200, 0., 100]) * units.m x3 = np.array([500, 0., 100]) * units.m x4 = np.array([1000, 0., 100]) * units.m x5 = np.array([10000, 0., 100]) * units.m -N = 4 + +x_starts = [x1, x1, x1, x1, x5] +x_stops = [x2, x3, x4, x5, x1] +N = 5 receive_vectors = np.zeros((N, 2, 3)) * np.nan ray_tracing_C0 = np.zeros((N, 2)) * np.nan @@ -27,14 +32,12 @@ ice = medium.southpole_simple() -lss = ['-', '--', ':'] -colors = ['b', 'g', 'r'] fig, ax = plt.subplots(1, 1) -ax.plot(x1[0], x1[2], 'ko') -for i, x in enumerate([x2, x3, x4, x5]): - print('finding solutions for ', x) +for i, (x_start, x_stop) in enumerate(zip(x_starts, x_stops)): + ax.plot(x_start[0], x_start[2], 'ko') + print(f'finding solutions for {x_start} to {x_stop}') r = ray.ray_tracing(ice, log_level=logging.WARNING, use_cpp=False) - r.set_start_and_end_point(x1, x) + r.set_start_and_end_point(x_start, x_stop) r.find_solutions() if(r.has_solution()): for iS in range(r.get_number_of_solutions()): @@ -57,20 +60,28 @@ att = r.get_attenuation(iS, np.array([100, 200]) * units.MHz) print(att) + + + efield=NuRadioReco.framework.electric_field.ElectricField([0]) + efield.set_trace(np.ones((3,200)), 1) + efield2 = r.apply_propagation_effects(efield, 0) + + + xx, zz = r.get_ray_path(iS) - # to readout the actual trace, we have to flatten to 2D - dX = x - x1 - dPhi = -np.arctan2(dX[1], dX[0]) - c, s = np.cos(dPhi), np.sin(dPhi) - R = np.array(((c, -s, 0), (s, c, 0), (0, 0, 1))) - X1r = x1 - X2r = np.dot(R, x - x1) + x1 - x1_2d = np.array([X1r[0], X1r[2]]) - x2_2d = np.array([X2r[0], X2r[2]]) - r_2d = ray.ray_tracing_2D(ice) - yy, zz = r_2d.get_path(x1_2d, x2_2d, ray_tracing_C0[i, iS]) - ax.plot(yy, zz, '{}'.format(php.get_color_linestyle(i)), label='{} C0 = {:.4f}, f = {:.2f}'.format(ray_tracing_solution_type[i, iS], ray_tracing_C0[i, iS], focusing)) - ax.plot(x2_2d[0], x2_2d[1], '{}{}-'.format('d', php.get_color(i))) + # # to readout the actual trace, we have to flatten to 2D + # dX = x - x_start + # dPhi = -np.arctan2(dX[1], dX[0]) + # c, s = np.cos(dPhi), np.sin(dPhi) + # R = np.array(((c, -s, 0), (s, c, 0), (0, 0, 1))) + # X1r = x_start + # X2r = np.dot(R, x - x_start) + x_start + # x1_2d = np.array([X1r[0], X1r[2]]) + # x2_2d = np.array([X2r[0], X2r[2]]) + # r_2d = ray.ray_tracing_2D(ice) + # yy, zz = r_2d.get_path(x1_2d, x2_2d, ray_tracing_C0[i, iS]) + ax.plot(xx, zz, '{}'.format(php.get_color_linestyle(i)), label='{} C0 = {:.4f}, f = {:.2f}'.format(ray_tracing_solution_type[i, iS], ray_tracing_C0[i, iS], focusing)) + ax.plot(x_stop[0], x_stop[2], '{}{}-'.format('d', php.get_color(i))) ax.legend() ax.set_xlabel("y [m]") From 420cd2ec2bcb937b5824befdbac953ce081d460f Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Mon, 27 Nov 2023 13:51:16 +0000 Subject: [PATCH 22/54] improve logging --- NuRadioMC/SignalProp/analyticraytracing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index c86621a07..ba14c62fa 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -685,7 +685,6 @@ def get_attenuation_along_path(self, x1, x2, C_0, frequency, max_detector_freq, z_turn = 0 y_turn = self.get_y(self.get_gamma(z_turn), C_0, self.get_C_1(x1, C_0)) x2 = [y_turn, z_turn] - self.__logger.warning(f"transmission coefficient of propagation from ice into air, or air into ice is not taken into account in attenuation calculation!") if self.use_cpp: mask = frequency > 0 @@ -2311,18 +2310,19 @@ def apply_propagation_effects(self, efield, i_solution): continue if(self._x2[1] > 0): # we need to treat the case of air to ice/ice to air propagation sepatately: # air/ice propagation + self.__logger.warning(f"calculation of transmission coefficients and focussing factor for air/ice propagation is experimental and needs further validation") if(not self._swap): # ice to air case t_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_p( zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) t_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_s( zenith_reflection, n_2=1., n_1=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) - self.__logger.warning(f"propagating from ice to air: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") + self.__logger.info(f"propagating from ice to air: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") else: # air to ice t_theta = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_p( zenith_reflection, n_1=1., n_2=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) t_phi = NuRadioReco.utilities.geometryUtilities.get_fresnel_t_s( zenith_reflection, n_1=1., n_2=self._medium.get_index_of_refraction([self._X2[0], self._X2[1], -1 * units.cm])) - self.__logger.warning(f"propagating from air to ice: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") + self.__logger.info(f"propagating from air to ice: transmission coefficient is {t_theta:.2f}, {t_phi:.2f}") spec[1] *= t_theta spec[2] *= t_phi else: @@ -2336,7 +2336,7 @@ def apply_propagation_effects(self, efield, i_solution): spec[1] *= r_theta spec[2] *= r_phi - self.__logger.warning( + self.__logger.info( "ray hits the surface at an angle {:.2f}deg -> reflection coefficient is r_theta = {:.2f}, r_phi = {:.2f}".format( zenith_reflection / units.deg, r_theta, r_phi)) From bea87725491deb8e26cba360dbd6f71071a546c4 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Thu, 30 Nov 2023 12:23:22 +0000 Subject: [PATCH 23/54] fix code to allow for more vertical propagation direction for air/ice case. --- NuRadioMC/SignalProp/analyticraytracing.py | 76 ++++++++++++---------- NuRadioMC/SignalProp/examples/E02ToAir.py | 15 ++++- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index ba14c62fa..4788d6b26 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -1139,12 +1139,32 @@ def get_delta_y(self, C_0, x1, x2, C0range=None, reflection=0, reflection_case=2 # between the turning point and the target point + 10 x the distance between the z position of the turning points # and the target position. This results in a objective function that has the solutions as the only minima and # is smooth in C_0 + diff = ((z_turn - x2[1]) ** 2 + (y_turn - x2[0]) ** 2) ** 0.5 + 10 * np.abs(z_turn - x2[1]) self.__logger.debug( - "turning points (zturn = {:.0f} is deeper than x2 positon z2 = {:.0f}, setting distance to target position to {:.1f}".format(z_turn, x2[1], -diff)) + "turning points (zturn = {:.4g} is deeper than x2 positon z2 = {:.0f}, setting distance to target position to {:.1f}".format(z_turn, x2[1], -diff)) return -diff # return -np.inf self.__logger.debug('turning points is z = {:.1f}, y = {:.1f}'.format(z_turn, y_turn)) + + if(x2[1] > 0): # first treat the ice to air case + # Do nothing if ray is refracted. If ray is reflected, don't mirror but do straight line upwards + if(z_turn == 0): + zenith_reflection = self.get_reflection_angle(x1, x2, C_0, reflection, reflection_case) + if(zenith_reflection == None): + diff = x2[1] + self.__logger.debug(f"not refracting into air") + return diff + n_1 = self.medium.get_index_of_refraction([y_turn, 0, z_turn]) + zenith_air = NuRadioReco.utilities.geometryUtilities.get_fresnel_angle(zenith_reflection, n_1=n_1, n_2=1) + if(zenith_air is None): + diff = x2[1] + self.__logger.debug(f"not refracting into air") + return diff + z = (x2[0] - y_turn) / np.tan(zenith_air) + diff = x2[1] - z + self.__logger.debug(f"touching surface at {zenith_reflection/units.deg:.1f}deg -> {zenith_air/units.deg:.1f}deg -> x2 = {x2} diff {diff:.2f}") + return diff if(y_turn > x2[0]): # we always propagate from left to right # direct ray y2_fit = self.get_y(self.get_gamma(x2[1]), C_0, C_1) # calculate y position at get_path position @@ -1158,35 +1178,19 @@ def get_delta_y(self, C_0, x1, x2, C0range=None, reflection=0, reflection_case=2 'we have a direct ray, y({:.1f}) = {:.1f} -> {:.1f} away from {:.1f}, turning point = y={:.1f}, z={:.2f}, x0 = {:.1f} {:.1f}'.format(x2[1], y2_fit, diff, x2[0], y_turn, z_turn, x1[0], x1[1])) return diff else: + # now it's a bit more complicated. we need to transform the coordinates to + # be on the mirrored part of the function + z_mirrored = x2[1] + gamma = self.get_gamma(z_mirrored) + self.__logger.debug("get_y( {}, {}, {})".format(gamma, C_0, C_1)) + y2_raw = self.get_y(gamma, C_0, C_1) + y2_fit = 2 * y_turn - y2_raw + diff = (x2[0] - y2_fit) - if(x2[1] > 0): # first treat the ice to air case - # Do nothing if ray is refracted. If ray is reflected, don't mirror but do straight line upwards - if(z_turn == 0): - zenith_reflection = self.get_reflection_angle(x1, x2, C_0, reflection, reflection_case) - n_1 = self.medium.get_index_of_refraction([y_turn, 0, z_turn]) - zenith_air = NuRadioReco.utilities.geometryUtilities.get_fresnel_angle(zenith_reflection, n_1=n_1, n_2=1) - if(zenith_air is None): - diff = x2[0] - self.__logger.debug(f"not refracting into air") - return diff - z = (x2[0] - y_turn) / np.tan(zenith_air) - diff = x2[1] - z - self.__logger.debug(f"touching surface at {zenith_reflection/units.deg:.1f}deg -> {zenith_air/units.deg:.1f}deg -> x2 = {x2} diff {diff:.2f}") - return diff - else: - # now it's a bit more complicated. we need to transform the coordinates to - # be on the mirrored part of the function - z_mirrored = x2[1] - gamma = self.get_gamma(z_mirrored) - self.__logger.debug("get_y( {}, {}, {})".format(gamma, C_0, C_1)) - y2_raw = self.get_y(gamma, C_0, C_1) - y2_fit = 2 * y_turn - y2_raw - diff = (x2[0] - y2_fit) - - self.__logger.debug('we have a reflected/refracted ray, y({:.1f}) = {:.1f} ({:.1f}) -> {:.1f} away from {:.1f} (gamma = {:.5g})'.format( - z_mirrored, y2_fit, y2_raw, diff, x2[0], gamma)) - - return -1 * diff + self.__logger.debug('we have a reflected/refracted ray, y({:.1f}) = {:.1f} ({:.1f}) -> {:.1f} away from {:.1f} (gamma = {:.5g})'.format( + z_mirrored, y2_fit, y2_raw, diff, x2[0], gamma)) + + return -1 * diff def determine_solution_type(self, x1, x2, C_0): """ returns the type of the solution @@ -1264,19 +1268,21 @@ def find_solutions(self, x1, x2, plot=False, reflection=0, reflection_case=1): if(x2[1] > 0): # special case of ice to air ray tracing. There is always one unique solution between C_0 = inf and C_0 that # skims the surface. Therefore, we can find the solution using an efficient root finding algorithm. - logC0_start = 1 # infinity is bad, 1 is steep enough + logC0_start = 100 # infinity is bad, 100 is steep enough C_0_stop = self.get_C_0_from_angle(np.arcsin(1/self.medium.get_index_of_refraction([0, x1[0], x1[1]])), x1[1]).x[0] logC0_stop = np.log(C_0_stop - 1/self.medium.n_ice) - self.__logger.debug( - "Looking for ice-air solutions between C0 ({}, {}) with delta_y ({}, {})".format( - logC0_start, logC0_stop, *[self.obj_delta_y(logC0, x1, x2, reflection, reflection_case) for logC0 in [logC0_start, logC0_stop]] - )) + delta_ys = [self.obj_delta_y(logC0, x1, x2, reflection, reflection_case) for logC0 in [logC0_start, logC0_stop]] + self.__logger.debug("Looking for ice-air solutions between log(C0) ({}, {}) with delta_y ({}, {})".format(logC0_start, logC0_stop, *delta_ys)) + if(np.sign(delta_ys[0]) == np.sign(delta_ys[1])): + self.__logger.warning(f"can't find a solution for ice/air propagation. The trajectory might be too vertical! This is currently not"\ + " supported because of numerical instabilities.") + return results result = optimize.brentq(self.obj_delta_y, logC0_start, logC0_stop, args=(x1, x2, reflection, reflection_case)) C_0 = self.get_C0_from_log(result) C0s.append(C_0) solution_type = self.determine_solution_type(x1, x2, C_0) - self.__logger.info("found {} solution C0 = {:.2f}".format(solution_types[solution_type], C_0)) + self.__logger.info("found {} solution C0 = {:.2f} (internal logC = {:.2f})".format(solution_types[solution_type], C_0, result)) results.append({'type': solution_type, 'C0': C_0, 'C1': self.get_C_1(x1, C_0), diff --git a/NuRadioMC/SignalProp/examples/E02ToAir.py b/NuRadioMC/SignalProp/examples/E02ToAir.py index 1ef30857f..44237d1bf 100644 --- a/NuRadioMC/SignalProp/examples/E02ToAir.py +++ b/NuRadioMC/SignalProp/examples/E02ToAir.py @@ -14,8 +14,8 @@ x1 = np.array([0, 0., -149.]) * units.m -x2 = np.array([200, 0., 100]) * units.m -x3 = np.array([500, 0., 100]) * units.m +x2 = np.array([0, 0., 100]) * units.m +x3 = np.array([200, 0., 100]) * units.m x4 = np.array([1000, 0., 100]) * units.m x5 = np.array([10000, 0., 100]) * units.m @@ -32,6 +32,15 @@ ice = medium.southpole_simple() +if 0: # for debug purpuses, plot the objective function + fig2, ax2 = plt.subplots(1, 1) + for i, (x_start, x_stop) in enumerate(zip(x_starts, x_stops)): + r2d = ray.ray_tracing_2D(ice, log_level=logging.WARNING) + logC0s = np.linspace(-0.9, 10, 10) + oo = [r2d.obj_delta_y(t, x_start[np.array([0,2])], x_stop[np.array([0,2])]) for t in logC0s] + ax2.plot(logC0s, oo, "-o") + plt.show() + fig, ax = plt.subplots(1, 1) for i, (x_start, x_stop) in enumerate(zip(x_starts, x_stops)): ax.plot(x_start[0], x_start[2], 'ko') @@ -59,7 +68,7 @@ print(f" focusing factor = {focusing:.8f}") att = r.get_attenuation(iS, np.array([100, 200]) * units.MHz) - print(att) + print(f" attenuation: {att}") efield=NuRadioReco.framework.electric_field.ElectricField([0]) From 799d78985715af4690eb5dd84082cb271c49bfc6 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Sat, 2 Dec 2023 10:58:31 +0100 Subject: [PATCH 24/54] correct for n in focussing factor only until ice/air boundary to avoid double-counting with fresnel coefficients --- NuRadioMC/SignalProp/analyticraytracing.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/NuRadioMC/SignalProp/analyticraytracing.py b/NuRadioMC/SignalProp/analyticraytracing.py index 4788d6b26..4215be25f 100644 --- a/NuRadioMC/SignalProp/analyticraytracing.py +++ b/NuRadioMC/SignalProp/analyticraytracing.py @@ -2245,12 +2245,17 @@ def get_focusing(self, iS, dz=-1. * units.cm, limit=2.): focusing = limit # now also correct for differences in refractive index between emitter and receiver position + # for ice-to-air transmission, the fresnel coefficients account for this at the boundary already, + # so in that case we only take the difference up to the ice-air boundary + z_max = -0.01 * units.m + z1 = np.min([self._X1[-1], z_max]) + z2 = np.min([self._X2[-1], z_max]) if self._swap: - n1 = self._medium.get_index_of_refraction(self._X2) # emitter - n2 = self._medium.get_index_of_refraction(self._X1) # receiver + n1 = self._medium.get_index_of_refraction([0, 0, z2]) # emitter + n2 = self._medium.get_index_of_refraction([0, 0, z1]) # receiver else: - n1 = self._medium.get_index_of_refraction(self._X1) # emitter - n2 = self._medium.get_index_of_refraction(self._X2) # receiver + n1 = self._medium.get_index_of_refraction([0, 0, z1]) # emitter + n2 = self._medium.get_index_of_refraction([0, 0, z2]) # receiver return focusing * (n1 / n2) ** 0.5 def get_ray_path(self, iS): From 6c6ebd0fa7efab431b638aaec2abdda4ceb0fea1 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 6 Dec 2023 13:44:31 -0600 Subject: [PATCH 25/54] Add missing LPDA and fix LPDA rotation --- .../detector/RNO_G/RNO_season_2023.json | 132 ++++++++++-------- 1 file changed, 75 insertions(+), 57 deletions(-) diff --git a/NuRadioReco/detector/RNO_G/RNO_season_2023.json b/NuRadioReco/detector/RNO_G/RNO_season_2023.json index b1be43208..801612dcd 100644 --- a/NuRadioReco/detector/RNO_G/RNO_season_2023.json +++ b/NuRadioReco/detector/RNO_G/RNO_season_2023.json @@ -327,9 +327,9 @@ "18": { "station_id": 11, "channel_id": 12, - "ant_rotation_phi": 243.0, + "ant_rotation_phi": 220.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 333.0, + "ant_orientation_phi": 310.0, "ant_orientation_theta": 120.0, "ant_position_x": 25.245374429777485, "ant_position_y": -9.271898202581497, @@ -345,7 +345,7 @@ "19": { "station_id": 11, "channel_id": 13, - "ant_rotation_phi": 243.0, + "ant_rotation_phi": 220.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, "ant_orientation_theta": 0.0, @@ -363,9 +363,9 @@ "20": { "station_id": 11, "channel_id": 14, - "ant_rotation_phi": 243.0, + "ant_rotation_phi": 220.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 153.0, + "ant_orientation_phi": 130.0, "ant_orientation_theta": 120.0, "ant_position_x": 23.709240577868513, "ant_position_y": -4.359449327425068, @@ -381,9 +381,9 @@ "21": { "station_id": 11, "channel_id": 15, - "ant_rotation_phi": 351.0, + "ant_rotation_phi": 328.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 81.0, + "ant_orientation_phi": 58.0, "ant_orientation_theta": 120.0, "ant_position_x": 13.659226614984846, "ant_position_y": -4.116966752974577, @@ -399,7 +399,7 @@ "22": { "station_id": 11, "channel_id": 16, - "ant_rotation_phi": 351.0, + "ant_rotation_phi": 328.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, "ant_orientation_theta": 0.0, @@ -417,9 +417,9 @@ "23": { "station_id": 11, "channel_id": 17, - "ant_rotation_phi": 351.0, + "ant_rotation_phi": 328.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 261.0, + "ant_orientation_phi": 238.0, "ant_orientation_theta": 120.0, "ant_position_x": 11.212162270289355, "ant_position_y": -9.197601282929554, @@ -1569,9 +1569,9 @@ "87": { "station_id": 21, "channel_id": 15, - "ant_rotation_phi": 254.0, + "ant_rotation_phi": 277.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 164.0, + "ant_orientation_phi": 187.0, "ant_orientation_theta": 120.0, "ant_position_x": -20.913616334864287, "ant_position_y": 16.537611216532184, @@ -1587,7 +1587,7 @@ "88": { "station_id": 21, "channel_id": 16, - "ant_rotation_phi": 254.0, + "ant_rotation_phi": 277.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, "ant_orientation_theta": 0.0, @@ -1605,9 +1605,9 @@ "89": { "station_id": 21, "channel_id": 17, - "ant_rotation_phi": 254.0, + "ant_rotation_phi": 277.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 344.0, + "ant_orientation_phi": 7.0, "ant_orientation_theta": 120.0, "ant_position_x": -15.404148418065631, "ant_position_y": 17.46318633982372, @@ -1623,9 +1623,9 @@ "90": { "station_id": 21, "channel_id": 14, - "ant_rotation_phi": 65.0, + "ant_rotation_phi": 42.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 155.0, + "ant_orientation_phi": 132.0, "ant_orientation_theta": 120.0, "ant_position_x": -25.02971666518448, "ant_position_y": 7.293175091846081, @@ -1641,7 +1641,7 @@ "91": { "station_id": 21, "channel_id": 13, - "ant_rotation_phi": 65.0, + "ant_rotation_phi": 42.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, "ant_orientation_theta": 0.0, @@ -1659,9 +1659,9 @@ "92": { "station_id": 21, "channel_id": 12, - "ant_rotation_phi": 65.0, + "ant_rotation_phi": 42.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 335.0, + "ant_orientation_phi": 312.0, "ant_orientation_theta": 120.0, "ant_position_x": -22.479689741668324, "ant_position_y": 2.80783833285102, @@ -1677,9 +1677,9 @@ "93": { "station_id": 21, "channel_id": 20, - "ant_rotation_phi": 118.0, + "ant_rotation_phi": 141.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 208.0, + "ant_orientation_phi": 231.0, "ant_orientation_theta": 120.0, "ant_position_x": -11.266588048738129, "ant_position_y": 2.9011467712219314, @@ -1695,7 +1695,7 @@ "94": { "station_id": 21, "channel_id": 19, - "ant_rotation_phi": 118.0, + "ant_rotation_phi": 141.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, "ant_orientation_theta": 0.0, @@ -1713,9 +1713,9 @@ "95": { "station_id": 21, "channel_id": 18, - "ant_rotation_phi": 118.0, + "ant_rotation_phi": 141.0, "ant_rotation_theta": 90.0, - "ant_orientation_phi": 28.0, + "ant_orientation_phi": 51.0, "ant_orientation_theta": 120.0, "ant_position_x": -8.513397338379718, "ant_position_y": 7.724537269470318, @@ -2270,7 +2270,7 @@ }, "126": { "station_id": 23, - "channel_id": 7, + "channel_id": 6, "ant_rotation_phi": 90.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, @@ -2288,7 +2288,7 @@ }, "127": { "station_id": 23, - "channel_id": 6, + "channel_id": 7, "ant_rotation_phi": 90.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, @@ -2467,6 +2467,24 @@ "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, "137": { + "station_id": 23, + "channel_id": 12, + "ant_rotation_phi": 270.0, + "ant_rotation_theta": 90.0, + "ant_orientation_phi": 0.0, + "ant_orientation_theta": 120.0, + "ant_position_x": -13.716551579004388, + "ant_position_y": 17.24043830214714, + "ant_position_z": -0.5, + "amp_type": "rno_surface", + "cab_time_delay": 45.78713480723031, + "adc_n_samples": 2048, + "adc_sampling_frequency": 3.2, + "commission_time": "{TinyDate}:2022-06-24T00:00:00", + "decommission_time": "{TinyDate}:2035-11-01T00:00:00", + "ant_type": "createLPDA_100MHz_InfFirn_n1.4" + }, + "138": { "station_id": 23, "channel_id": 20, "ant_rotation_phi": 150.0, @@ -2484,7 +2502,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "138": { + "139": { "station_id": 23, "channel_id": 19, "ant_rotation_phi": 150.0, @@ -2502,7 +2520,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "139": { + "140": { "station_id": 23, "channel_id": 18, "ant_rotation_phi": 150.0, @@ -2520,7 +2538,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "140": { + "141": { "station_id": 23, "channel_id": 17, "ant_rotation_phi": 30.0, @@ -2538,7 +2556,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "141": { + "142": { "station_id": 23, "channel_id": 16, "ant_rotation_phi": 30.0, @@ -2556,7 +2574,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "142": { + "143": { "station_id": 23, "channel_id": 15, "ant_rotation_phi": 30.0, @@ -2574,7 +2592,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "143": { + "144": { "station_id": 24, "channel_id": 9, "ant_rotation_phi": 90.0, @@ -2592,7 +2610,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "144": { + "145": { "station_id": 24, "channel_id": 10, "ant_rotation_phi": 90.0, @@ -2610,7 +2628,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "145": { + "146": { "station_id": 24, "channel_id": 11, "ant_rotation_phi": 90.0, @@ -2628,7 +2646,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" }, - "146": { + "147": { "station_id": 24, "channel_id": 0, "ant_rotation_phi": 90.0, @@ -2646,7 +2664,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "147": { + "148": { "station_id": 24, "channel_id": 1, "ant_rotation_phi": 90.0, @@ -2664,7 +2682,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "148": { + "149": { "station_id": 24, "channel_id": 2, "ant_rotation_phi": 90.0, @@ -2682,7 +2700,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "149": { + "150": { "station_id": 24, "channel_id": 3, "ant_rotation_phi": 90.0, @@ -2700,7 +2718,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "150": { + "151": { "station_id": 24, "channel_id": 4, "ant_rotation_phi": 90.0, @@ -2718,7 +2736,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" }, - "151": { + "152": { "station_id": 24, "channel_id": 5, "ant_rotation_phi": 90.0, @@ -2736,7 +2754,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "152": { + "153": { "station_id": 24, "channel_id": 6, "ant_rotation_phi": 90.0, @@ -2754,7 +2772,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "153": { + "154": { "station_id": 24, "channel_id": 7, "ant_rotation_phi": 90.0, @@ -2772,7 +2790,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "154": { + "155": { "station_id": 24, "channel_id": 8, "ant_rotation_phi": 90.0, @@ -2790,7 +2808,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" }, - "155": { + "156": { "station_id": 24, "channel_id": 21, "ant_rotation_phi": 90.0, @@ -2808,7 +2826,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_quadslot_v3_air_rescaled_to_n1.74" }, - "156": { + "157": { "station_id": 24, "channel_id": 22, "ant_rotation_phi": 90.0, @@ -2826,7 +2844,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "157": { + "158": { "station_id": 24, "channel_id": 23, "ant_rotation_phi": 90.0, @@ -2844,7 +2862,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "RNOG_vpol_4inch_center_n1.73" }, - "158": { + "159": { "station_id": 24, "channel_id": 15, "ant_rotation_phi": 330.0, @@ -2862,7 +2880,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "159": { + "160": { "station_id": 24, "channel_id": 16, "ant_rotation_phi": 330.0, @@ -2880,7 +2898,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "160": { + "161": { "station_id": 24, "channel_id": 17, "ant_rotation_phi": 330.0, @@ -2898,7 +2916,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "161": { + "162": { "station_id": 24, "channel_id": 18, "ant_rotation_phi": 90.0, @@ -2916,7 +2934,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "162": { + "163": { "station_id": 24, "channel_id": 19, "ant_rotation_phi": 90.0, @@ -2934,7 +2952,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "163": { + "164": { "station_id": 24, "channel_id": 20, "ant_rotation_phi": 90.0, @@ -2952,7 +2970,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "164": { + "165": { "station_id": 24, "channel_id": 12, "ant_rotation_phi": 210.0, @@ -2970,7 +2988,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "165": { + "166": { "station_id": 24, "channel_id": 13, "ant_rotation_phi": 210.0, @@ -2988,7 +3006,7 @@ "decommission_time": "{TinyDate}:2035-11-01T00:00:00", "ant_type": "createLPDA_100MHz_InfFirn_n1.4" }, - "166": { + "167": { "station_id": 24, "channel_id": 14, "ant_rotation_phi": 210.0, @@ -3368,4 +3386,4 @@ "station_id": 24 } } -} +} \ No newline at end of file From 1928b2706e0ec5515bce331a9f843751b44cef63 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Thu, 7 Dec 2023 14:05:54 +0000 Subject: [PATCH 26/54] add new feature to changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index bc242b7bc..5fe8b7c56 100644 --- a/changelog.txt +++ b/changelog.txt @@ -14,6 +14,7 @@ new features: - added ability to generate high-low-triggered noise on a narrow band but return full-band waveforms - phased array noise generation utility class: calculate trigger time and cut trace accordingly - use Philox noise generator in noise adder module (this changes the default random sequence) +- allow raytracing from air to ice and vice versa. Only supported by the Python implementation. (Note that the calculation of the focussing factor was not thoroughly tested.) bugfixes: - fixed/improved C++ raytracer not finding solutions for some near-horizontal or near-shadowzone vertices From c809cfe645c9462aed46e48993537e7fabc1c529 Mon Sep 17 00:00:00 2001 From: Christoph Date: Thu, 7 Dec 2023 10:15:59 -0600 Subject: [PATCH 27/54] Switch channels 6 and 7 on station 23 --- NuRadioReco/detector/RNO_G/RNO_season_2023.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NuRadioReco/detector/RNO_G/RNO_season_2023.json b/NuRadioReco/detector/RNO_G/RNO_season_2023.json index 801612dcd..52da48e43 100644 --- a/NuRadioReco/detector/RNO_G/RNO_season_2023.json +++ b/NuRadioReco/detector/RNO_G/RNO_season_2023.json @@ -2270,7 +2270,7 @@ }, "126": { "station_id": 23, - "channel_id": 6, + "channel_id": 7, "ant_rotation_phi": 90.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, @@ -2288,7 +2288,7 @@ }, "127": { "station_id": 23, - "channel_id": 7, + "channel_id": 6, "ant_rotation_phi": 90.0, "ant_rotation_theta": 90.0, "ant_orientation_phi": 0.0, @@ -3386,4 +3386,4 @@ "station_id": 24 } } -} \ No newline at end of file +} From 3f1e49a8294cfba0d142c8f800578b436e0a8491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Mon, 11 Dec 2023 12:04:04 +0100 Subject: [PATCH 28/54] Revert to previous calculation of **bandwidth**. Rename **bandwidth** tobe filter_response. Adjust logging. --- NuRadioMC/simulation/simulation.py | 43 +++++++++++++++++------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index ce0556b05..e72321a9d 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -265,7 +265,7 @@ def __init__(self, inputfilename, ################################ # perfom a dummy detector simulation to determine how the signals are filtered - self._bandwidth_per_channel = {} + self._filter_response_per_channel = {} self._amplification_per_channel = {} self.__noise_adder_normalization = {} @@ -302,7 +302,7 @@ def __init__(self, inputfilename, self._evt.set_station(self._station) self._detector_simulation_filter_amp(self._evt, self._station, self._det) - self._bandwidth_per_channel[self._station_id] = {} + self._filter_response_per_channel[self._station_id] = {} self._amplification_per_channel[self._station_id] = {} for channel_id in range(self._det.get_number_of_channels(self._station_id)): ff = np.linspace(0, 0.5 / self._dt, 10000) @@ -311,16 +311,15 @@ def __init__(self, inputfilename, if hasattr(instance, "get_filter"): filt *= instance.get_filter(ff, self._station_id, channel_id, self._det, **kwargs) - self._amplification_per_channel[self._station_id][channel_id] = np.abs(filt).max() - in_bandwith_lim = self._amplification_per_channel[self._station_id][channel_id] / 100 # a factor of 100 corresponds to -40 dB in amplitude - ff_in_bandwith = ff[np.abs(filt) > in_bandwith_lim] - bandwidth = ff_in_bandwith[-1] - ff_in_bandwith[0] - self._bandwidth_per_channel[self._station_id][channel_id] = bandwidth - logger.status(f"bandwidth of station {self._station_id} channel {channel_id} is {bandwidth / units.MHz:.1f} MHz") + self._amplification_per_channel[self._station_id][channel_id] = np.mean( + np.abs(filt)[np.abs(filt) > np.abs(filt).max() / 100]) # a factor of 100 corresponds to -40 dB in amplitude + filter_response = np.trapz(np.abs(filt) ** 2, ff) + self._filter_response_per_channel[self._station_id][channel_id] = filter_response + logger.debug(f"Estimated bandwidth of station {self._station_id} channel {channel_id} is {filter_response / self._amplification_per_channel[self._station_id][channel_id] ** 2 / units.MHz:.1f} MHz") ################################ - self._bandwidth = next(iter(next(iter(self._bandwidth_per_channel.values())).values())) + self._bandwidth = next(iter(next(iter(self._filter_response_per_channel.values())).values())) amplification = next(iter(next(iter(self._amplification_per_channel.values())).values())) noise_temp = self._cfg['trigger']['noise_temperature'] Vrms = self._cfg['trigger']['Vrms'] @@ -333,10 +332,10 @@ def __init__(self, inputfilename, self._noise_temp = float(noise_temp) self._Vrms_per_channel = {} self._noiseless_channels = {} - for station_id in self._bandwidth_per_channel: + for station_id in self._filter_response_per_channel: self._Vrms_per_channel[station_id] = {} self._noiseless_channels[station_id] = [] - for channel_id in self._bandwidth_per_channel[station_id]: + for channel_id in self._filter_response_per_channel[station_id]: if self._noise_temp is None: noise_temp_channel = self._det.get_noise_temperature(station_id, channel_id) else: @@ -344,11 +343,15 @@ def __init__(self, inputfilename, if self._det.is_channel_noiseless(station_id, channel_id): self._noiseless_channels[station_id].append(channel_id) + # from elog:1566 and https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise (last Eq. in "noise voltage and power" section self._Vrms_per_channel[station_id][channel_id] = (noise_temp_channel * 50 * constants.k * - self._bandwidth_per_channel[station_id][channel_id] / units.Hz) ** 0.5 # from elog:1566 and https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise (last Eq. in "noise voltage and power" section - logger.status(f'station {station_id} channel {channel_id} noise temperature = {noise_temp_channel}, bandwidth = {self._bandwidth_per_channel[station_id][channel_id]/ units.MHz:.2f} MHz -> Vrms = {self._Vrms_per_channel[station_id][channel_id]/ units.V / units.micro:.2f} muV') + self._filter_response_per_channel[station_id][channel_id] / units.Hz) ** 0.5 + logger.debug(f'station {station_id} channel {channel_id} noise temperature = {noise_temp_channel} K -> Vrms = ' + f'{self._Vrms_per_channel[station_id][channel_id]/ units.mV:.2f} mV') + self._Vrms = next(iter(next(iter(self._Vrms_per_channel.values())).values())) - logger.status('(if same bandwidth for all stations/channels is assumed:) noise temperature = {}, bandwidth = {:.2f} MHz -> Vrms = {:.2f} muV'.format(self._noise_temp, self._bandwidth / units.MHz, self._Vrms / units.V / units.micro)) + logger.status('(if same filter response for all stations/channels is assumed:) noise temperature = {}, est. bandwidth = {:.2f} MHz -> Vrms = {:.2f} muV'.format( + self._noise_temp, self._bandwidth / amplification ** 2 / units.MHz, self._Vrms / units.V / units.micro)) elif Vrms is not None: self._Vrms = float(Vrms) * units.V self._noise_temp = None @@ -356,13 +359,15 @@ def __init__(self, inputfilename, raise AttributeError(f"noise temperature and Vrms are both set to None") self._Vrms_efield_per_channel = {} - for station_id in self._bandwidth_per_channel: + for station_id in self._filter_response_per_channel: self._Vrms_efield_per_channel[station_id] = {} - for channel_id in self._bandwidth_per_channel[station_id]: + for channel_id in self._filter_response_per_channel[station_id]: self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms_per_channel[station_id][channel_id] / self._amplification_per_channel[station_id][channel_id] / units.m self._Vrms_efield = next(iter(next(iter(self._Vrms_efield_per_channel.values())).values())) tmp_cut = float(self._cfg['speedup']['min_efield_amplitude']) - logger.status(f"final Vrms {self._Vrms/units.V:.2g}V corresponds to an efield of {self._Vrms_efield/units.V/units.m/units.micro:.2g} muV/m for a VEL = 1m (amplification factor of system is {amplification:.1f}).\n -> all signals with less then {tmp_cut:.1f} x Vrms_efield = {tmp_cut * self._Vrms_efield/units.m/units.V/units.micro:.2g}muV/m will be skipped") + logger.status(f"final Vrms {self._Vrms/units.V:.2g}V corresponds to an efield of {self._Vrms_efield/units.V/units.m/units.micro:.2g} " + f"muV/m for a VEL = 1m (amplification factor of system is {amplification:.1f}).\n -> all signals with less then " + f"{tmp_cut:.1f} x Vrms_efield = {tmp_cut * self._Vrms_efield/units.m/units.V/units.micro:.2g}muV/m will be skipped") self._distance_cut_polynomial = None if self._cfg['speedup']['distance_cut']: @@ -956,7 +961,7 @@ def run(self): channel_ids = self._det.get_channel_ids(self._station.get_id()) Vrms = {} for channel_id in channel_ids: - norm = self._bandwidth_per_channel[self._station.get_id()][channel_id] + norm = self._filter_response_per_channel[self._station.get_id()][channel_id] Vrms[channel_id] = self._Vrms_per_channel[self._station.get_id()][channel_id] / (norm / max_freq) ** 0.5 # normalize noise level to the bandwidth its generated for channelGenericNoiseAdder.run(self._evt, self._station, self._det, amplitude=Vrms, min_freq=0 * units.MHz, max_freq=max_freq, type='rayleigh', excluded_channels=self._noiseless_channels[station_id]) @@ -1452,7 +1457,7 @@ def _write_output_file(self, empty=False): positions[channel_id] = self._det.get_relative_position(station_id, channel_id) + self._det.get_absolute_position(station_id) fout["station_{:d}".format(station_id)].attrs['antenna_positions'] = positions fout["station_{:d}".format(station_id)].attrs['Vrms'] = list(self._Vrms_per_channel[station_id].values()) - fout["station_{:d}".format(station_id)].attrs['bandwidth'] = list(self._bandwidth_per_channel[station_id].values()) + fout["station_{:d}".format(station_id)].attrs['bandwidth'] = list(self._filter_response_per_channel[station_id].values()) fout.attrs.create("Tnoise", self._noise_temp, dtype=float) fout.attrs.create("Vrms", self._Vrms, dtype=float) From 7701b22d1d32234f56422c7346a30f200b385c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Fri, 15 Dec 2023 14:36:02 +0100 Subject: [PATCH 29/54] Update documentation --- .../source/NuRadioMC/pages/HDF5_structure.rst | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/documentation/source/NuRadioMC/pages/HDF5_structure.rst b/documentation/source/NuRadioMC/pages/HDF5_structure.rst index aaa4ee51f..9d5d008b4 100644 --- a/documentation/source/NuRadioMC/pages/HDF5_structure.rst +++ b/documentation/source/NuRadioMC/pages/HDF5_structure.rst @@ -45,7 +45,7 @@ The HDF5 files can be thought of as a structured dictionary: - The top level :ref:`attributes `, which can be accessed through ``f.attrs``, contain some top-level information about the simulation. - The :ref:`individual keys ` contain some properties (energy, vertex, ...) for each stored event or shower. -- Finally, the ``station_`` key contains slightly more detailed information (triggers, propagation times, amplitudes...) at the level of individual channels :ref:`for each station `. +- Finally, the ``station_`` key contains slightly more detailed information (triggers, propagation times, amplitudes...) at the level of individual channels :ref:`for each station `. Each station group has its own attributes (``f[station_].attrs``) HDF5 file attributes ____________________ @@ -72,14 +72,26 @@ The top-level attributes can be accessed using ``f.attrs``. These contain: ``start_event_id`` | ``event_id`` of the first event in the file ``trigger_names`` | List of the names of the different triggers simulated ``Tnoise`` | (explicit) noise temperature used in simulation - ``Vrms`` | RMS of the voltage used as thermal noise floor. Determine from ``Tnoise`` and ``bandwidth`` - ``bandwidth`` | Bandwidth of the antennas/detector (for triggering) ``n_samples`` | Samples of the to-be generated antenna signals ``config`` | The (yaml-style) config file used for the simulation ``deposited`` | ``detector`` | The (json-format) detector description used for the simulation ``dt`` | The time resolution, i.e. the inverse of the sampling rate used for the simulation. This is not necessarily the same as the sampling rate of the simulated channels! + +The station-level attributes can be accessed using ``f[station_].attrs``. The first two attributes ``Vrms`` and ``bandwidth`` also exist on the top-level and refer to the corresponding to the first station/channel pair. + + .. _hdf5-station-attrs-table: + + .. csv-table:: HDF5 station attributes + :header: "Key", "Description" + :widths: auto + :delim: | + + ``Vrms`` | RMS of the voltage used as thermal noise floor :math:`v_{n} = (k_{B} \, R \, T \, \Delta f) ^ {0.5}`. See the relevant section "Noise voltage and power" in this `wiki article `_ (last two equations). Determine from ``Tnoise`` and ``bandwidth`` (see below). + ``bandwidth`` | Bandwidth is above equation. Calculated as the integral over the simulated filter response (`filt`) squared: :math:`\Delta f = np.trapz(np.abs(filt) ** 2, ff)`. + ``antenna_positions`` | Relative position of all simulated antennas (channels) + HDF5 file contents __________________ The HDF5 file contains the following items. Listed are the ``key`` and the ``shape`` of each HDF5 dataset, where ``n_events`` is the number of events stored in the file and ``n_showers`` @@ -114,12 +126,12 @@ is the number of showers (which may be larger than the number of events), and `` Station data ____________ In addition, the HDF5 file contains a key for each station in the simulation. -The station contains more detailed information for each station. Some parameters are per event and -some parameters are per shower. See https://doi.org/10.22323/1.395.1231 for a description of how showers relate to events. +The station contains more detailed information for each station. Some parameters are per event and +some parameters are per shower. See https://doi.org/10.22323/1.395.1231 for a description of how showers relate to events. ``m_events`` and ``m_showers`` refer to the number of events and showers that triggered the station. NOTE: The simple table structure of hdf5 files can not capture the complex relation between events and showers in all cases. Some fields can be ambiguous (e.g. `trigger_times` that only lists the last trigger that a shower generated). -For more advanced analyses, please use the ``*.nur`` files. +For more advanced analyses, please use the ``*.nur`` files. The ``event_group_id`` is the same as in the global dictionary. Therefore you can check for one event with an ``event_group_id`` which stations contain the same ``event_group_id`` and retrieve the information, which station triggered, with which amplitude, etc. The same approach works for ``shower_id``. From 12583bb567de361aef4a7b58e60bd3c9ccd4c92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Fri, 15 Dec 2023 14:52:00 +0100 Subject: [PATCH 30/54] Update variable names and logging --- NuRadioMC/simulation/simulation.py | 60 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index e72321a9d..7d983ba36 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -265,8 +265,9 @@ def __init__(self, inputfilename, ################################ # perfom a dummy detector simulation to determine how the signals are filtered - self._filter_response_per_channel = {} - self._amplification_per_channel = {} + self._integrated_channel_response = {} + self._integrated_channel_response_normalization = {} + self._max_amplification_per_channel = {} self.__noise_adder_normalization = {} # first create dummy event and station with channels @@ -301,9 +302,13 @@ def __init__(self, inputfilename, self._station.set_station_time(self._evt_time) self._evt.set_station(self._station) + # run detector simulation self._detector_simulation_filter_amp(self._evt, self._station, self._det) - self._filter_response_per_channel[self._station_id] = {} - self._amplification_per_channel[self._station_id] = {} + + self._integrated_channel_response[self._station_id] = {} + self._integrated_channel_response_normalization[self._station_id] = {} + self._max_amplification_per_channel[self._station_id] = {} + for channel_id in range(self._det.get_number_of_channels(self._station_id)): ff = np.linspace(0, 0.5 / self._dt, 10000) filt = np.ones_like(ff, dtype=complex) @@ -311,31 +316,42 @@ def __init__(self, inputfilename, if hasattr(instance, "get_filter"): filt *= instance.get_filter(ff, self._station_id, channel_id, self._det, **kwargs) - self._amplification_per_channel[self._station_id][channel_id] = np.mean( - np.abs(filt)[np.abs(filt) > np.abs(filt).max() / 100]) # a factor of 100 corresponds to -40 dB in amplitude - filter_response = np.trapz(np.abs(filt) ** 2, ff) - self._filter_response_per_channel[self._station_id][channel_id] = filter_response - logger.debug(f"Estimated bandwidth of station {self._station_id} channel {channel_id} is {filter_response / self._amplification_per_channel[self._station_id][channel_id] ** 2 / units.MHz:.1f} MHz") + self._max_amplification_per_channel[self._station_id][channel_id] = np.abs(filt).max() + + mean_integrated_response = np.mean( + np.abs(filt)[np.abs(filt) > np.abs(filt).max() / 100] ** 2) # a factor of 100 corresponds to -40 dB in amplitude + self._integrated_channel_response_normalization[self._station_id][channel_id] = mean_integrated_response + + integrated_channel_response = np.trapz(np.abs(filt) ** 2, ff) + self._integrated_channel_response[self._station_id][channel_id] = integrated_channel_response + + logger.debug(f"Estimated bandwidth of station {self._station_id} channel {channel_id} is " + f"{integrated_channel_response / mean_integrated_response / units.MHz:.1f} MHz") ################################ - self._bandwidth = next(iter(next(iter(self._filter_response_per_channel.values())).values())) - amplification = next(iter(next(iter(self._amplification_per_channel.values())).values())) + self._bandwidth = next(iter(next(iter(self._integrated_channel_response.values())).values())) # get value of first station/channel key pair + norm = next(iter(next(iter(self._integrated_channel_response_normalization.values())).values())) + amplification = next(iter(next(iter(self._max_amplification_per_channel.values())).values())) + noise_temp = self._cfg['trigger']['noise_temperature'] Vrms = self._cfg['trigger']['Vrms'] + if noise_temp is not None and Vrms is not None: raise AttributeError(f"Specifying noise temperature (set to {noise_temp}) and Vrms (set to {Vrms} is not allowed.") + if noise_temp is not None: if noise_temp == "detector": self._noise_temp = None # the noise temperature is defined in the detector description else: self._noise_temp = float(noise_temp) + self._Vrms_per_channel = {} self._noiseless_channels = {} - for station_id in self._filter_response_per_channel: + for station_id in self._integrated_channel_response: self._Vrms_per_channel[station_id] = {} self._noiseless_channels[station_id] = [] - for channel_id in self._filter_response_per_channel[station_id]: + for channel_id in self._integrated_channel_response[station_id]: if self._noise_temp is None: noise_temp_channel = self._det.get_noise_temperature(station_id, channel_id) else: @@ -345,13 +361,14 @@ def __init__(self, inputfilename, # from elog:1566 and https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise (last Eq. in "noise voltage and power" section self._Vrms_per_channel[station_id][channel_id] = (noise_temp_channel * 50 * constants.k * - self._filter_response_per_channel[station_id][channel_id] / units.Hz) ** 0.5 + self._integrated_channel_response[station_id][channel_id] / units.Hz) ** 0.5 logger.debug(f'station {station_id} channel {channel_id} noise temperature = {noise_temp_channel} K -> Vrms = ' f'{self._Vrms_per_channel[station_id][channel_id]/ units.mV:.2f} mV') self._Vrms = next(iter(next(iter(self._Vrms_per_channel.values())).values())) - logger.status('(if same filter response for all stations/channels is assumed:) noise temperature = {}, est. bandwidth = {:.2f} MHz -> Vrms = {:.2f} muV'.format( - self._noise_temp, self._bandwidth / amplification ** 2 / units.MHz, self._Vrms / units.V / units.micro)) + logger.status('noise temperature = {}, est. bandwidth = {:.2f} MHz -> Vrms = {:.2f} muV (for station {:d} and channel {:d})'.format( + self._noise_temp, self._bandwidth / norm / units.MHz, self._Vrms / units.V / units.micro, self._station_ids[0], 0)) + elif Vrms is not None: self._Vrms = float(Vrms) * units.V self._noise_temp = None @@ -359,10 +376,11 @@ def __init__(self, inputfilename, raise AttributeError(f"noise temperature and Vrms are both set to None") self._Vrms_efield_per_channel = {} - for station_id in self._filter_response_per_channel: + for station_id in self._integrated_channel_response: self._Vrms_efield_per_channel[station_id] = {} - for channel_id in self._filter_response_per_channel[station_id]: - self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms_per_channel[station_id][channel_id] / self._amplification_per_channel[station_id][channel_id] / units.m + for channel_id in self._integrated_channel_response[station_id]: + self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms_per_channel[station_id][channel_id] / self._max_amplification_per_channel[station_id][channel_id] / units.m + self._Vrms_efield = next(iter(next(iter(self._Vrms_efield_per_channel.values())).values())) tmp_cut = float(self._cfg['speedup']['min_efield_amplitude']) logger.status(f"final Vrms {self._Vrms/units.V:.2g}V corresponds to an efield of {self._Vrms_efield/units.V/units.m/units.micro:.2g} " @@ -961,7 +979,7 @@ def run(self): channel_ids = self._det.get_channel_ids(self._station.get_id()) Vrms = {} for channel_id in channel_ids: - norm = self._filter_response_per_channel[self._station.get_id()][channel_id] + norm = self._integrated_channel_response[self._station.get_id()][channel_id] Vrms[channel_id] = self._Vrms_per_channel[self._station.get_id()][channel_id] / (norm / max_freq) ** 0.5 # normalize noise level to the bandwidth its generated for channelGenericNoiseAdder.run(self._evt, self._station, self._det, amplitude=Vrms, min_freq=0 * units.MHz, max_freq=max_freq, type='rayleigh', excluded_channels=self._noiseless_channels[station_id]) @@ -1457,7 +1475,7 @@ def _write_output_file(self, empty=False): positions[channel_id] = self._det.get_relative_position(station_id, channel_id) + self._det.get_absolute_position(station_id) fout["station_{:d}".format(station_id)].attrs['antenna_positions'] = positions fout["station_{:d}".format(station_id)].attrs['Vrms'] = list(self._Vrms_per_channel[station_id].values()) - fout["station_{:d}".format(station_id)].attrs['bandwidth'] = list(self._filter_response_per_channel[station_id].values()) + fout["station_{:d}".format(station_id)].attrs['bandwidth'] = list(self._integrated_channel_response[station_id].values()) fout.attrs.create("Tnoise", self._noise_temp, dtype=float) fout.attrs.create("Vrms", self._Vrms, dtype=float) From febd3078606cbe9286ee262fa7a41f311422b026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Fri, 15 Dec 2023 17:21:36 +0100 Subject: [PATCH 31/54] This commit mainly refactors the function set_volume_attributes and inproves the doc strings. It also adds the minor functionality to specify a shift in x and y for the center of the simulation volume --- NuRadioMC/EvtGen/generator.py | 308 ++++++++++++++++------------------ 1 file changed, 142 insertions(+), 166 deletions(-) diff --git a/NuRadioMC/EvtGen/generator.py b/NuRadioMC/EvtGen/generator.py index ae68676dc..21fc114cc 100644 --- a/NuRadioMC/EvtGen/generator.py +++ b/NuRadioMC/EvtGen/generator.py @@ -346,7 +346,7 @@ def get_energies(n_events, Emin, Emax, spectrum_type, rnd=None): * 'GZK-1+IceCube-nu-2017': a combination of the cosmogenic (GZK-1) and astrophysical (IceCube nu 2017) flux * 'GZK-1+IceCube-nu-2022': a combination of the cosmogenic (GZK-1) and astrophysical (IceCube nu 2022) flux * 'GZK-2+IceCube-nu-2022': a combination of the cosmogenic (GZK-2) and astrophysical (IceCube nu 2022) flux - + rnd: random generator object if None is provided, a new default random generator object is initialized """ @@ -408,26 +408,69 @@ def J(E): def set_volume_attributes(volume, proposal, attributes): """ - helper function that interprets the volume settings and sets the relevant quantities as hdf5 attributes. + Helper function that interprets the volume settings and sets the relevant quantities as attributes dictinary. + The relevant quantities to define the volume in which neutrinos are generated are (rmin, rmax, zmin, zmax) or + (xmin, xmax, ymin, ymax, zmin, zmax) based on the user settings (see explanation blow). + + The user can specify a "fiducial" (mandatory) and "full" (optional) volume. In the end only events are + considered (= passed to radio simulation) which produce a shower (over a configurable energy threshold) + within the fiducial volume. However, neutrino vertices might be generated outside this fiducial volume + but the generated charge lepton (mu or tau) reaches the fiducial volume and produces a trigger + (this obviously only makes sense when running proposal). Hence, a "full" volume can be defined in which + neutrinos interactions are generated. If the "full" volume is not specified in the case you run proposal, + the volume is determind using the energy dependent mean-free-path length of the tau. + + TL;DR + - The fiducial volume needs to be chosen large enough such that no events outside of it will trigger. + - The full volume is optional and most of the times it is not necessary to specify it (because it is + automatically determined). Parameters ---------- - volume: dictionarty - dict specifying the volume + + volume: dict + A dictionary specifying the simulation volume. Can be either a cylinder (specified with min/max radius and z-coordinate) + or a cube (specified with min/max x-, y-, z-coordinates). + + Cylinder: + + * fiducial_{rmin,rmax,zmin,zmax}: float + lower r / upper r / lower z / upper z coordinate of fiducial volume + + * full_{rmin,rmax,zmin,zmax}: float (optional) + lower r / upper r / lower z / upper z coordinate of simulated volume + + Cube: + + * fiducial_{xmin,xmax,ymin,ymax,zmin,zmax}: float + lower / upper x-, y-, z-coordinate of fiducial volume + + * full_{xmin,xmax,ymin,ymax,zmin,zmax}: float (optional) + lower / upper x-, y-, z-coordinate of simulated volume + + Optinal you can also define the horizontal center of the volume (if you want to displace it from the origin) + + * x0, y0: float + x-, y-coordinate this shift the center of the simulation volume + proposal: bool specifies if secondary interaction via proposal are calculated + attributes: dictionary dict storing hdf5 attributes """ + n_events = attributes['n_events'] - if("fiducial_rmax" in volume): # user specifies a cylinder - if('fiducial_rmin' in volume): - attributes['fiducial_rmin'] = volume['fiducial_rmin'] - else: - attributes['fiducial_rmin'] = 0 - attributes['fiducial_rmax'] = volume['fiducial_rmax'] - attributes['fiducial_zmin'] = volume['fiducial_zmin'] - attributes['fiducial_zmax'] = volume['fiducial_zmax'] + + attributes["x0"] = volume.get('x0', 0) + attributes["y0"] = volume.get('y0', 0) + + if "fiducial_rmax" in volume: # user specifies a cylinder + attributes['fiducial_rmin'] = volume.get('fiducial_rmin', 0) + + # copy keys + for key in ['fiducial_rmax', 'fiducial_zmin', 'fiducial_zmax']: + attributes[key] = volume[key] rmin = attributes['fiducial_rmin'] rmax = attributes['fiducial_rmax'] @@ -435,53 +478,59 @@ def set_volume_attributes(volume, proposal, attributes): zmax = attributes['fiducial_zmax'] volume_fiducial = np.pi * (rmax ** 2 - rmin ** 2) * (zmax - zmin) - if('full_rmax' in volume): + if 'full_rmax' in volume: rmax = volume['full_rmax'] - if('full_rmin' in volume): + if 'full_rmin' in volume: rmin = volume['full_rmin'] - if('full_zmax' in volume): + if 'full_zmax' in volume: zmax = volume['full_zmax'] - if('full_zmin' in volume): + if 'full_zmin' in volume: zmin = volume['full_zmin'] # We increase the radius of the cylinder according to the tau track length - if(proposal): + if proposal: logger.info("simulation of second interactions via PROPOSAL activated") - if("full_rmin" in volume): - rmin = volume['full_rmin'] - else: - rmin = attributes['fiducial_rmin'] / 3. - if('full_rmax' in volume): + rmin = volume.get('full_rmin', attributes['fiducial_rmin'] / 3.) + + if 'full_rmax' in volume: rmax = volume['full_rmax'] else: tau_95_length = get_tau_95_length(attributes['Emax']) # check if thetamax/thetamin are in same quadrant is_same_quadrant = np.sign(np.cos(attributes['thetamin'])) == np.sign(np.cos(attributes['thetamax'])) if is_same_quadrant: - max_horizontal_dist = np.abs(max(np.abs(np.tan(attributes['thetamin'])), np.abs(np.tan(attributes['thetamax']))) * volume['fiducial_zmin']) # calculates the maximum horizontal distance through the ice + max_horizontal_dist = np.abs(max(np.abs(np.tan(attributes['thetamin'])), + np.abs(np.tan(attributes['thetamax']))) * volume['fiducial_zmin']) # calculates the maximum horizontal distance through the ice else: # not same quadrant: theta range surpasses pi/2 -> horizontal trajectories # geometric trajectory is infinite, but not acutally used. distance is limited by the min() to allowed length below max_horizontal_dist = np.inf + d_extent = min(tau_95_length, max_horizontal_dist) - logger.info(f"95% quantile of tau decay length is {tau_95_length/units.km:.1f}km. Maximum horizontal distance for zenith range of {attributes['thetamin']/units.deg:.0f} - {attributes['thetamax']/units.deg:.0f}deg and depth of {volume['fiducial_zmin']/units.km:.1f}km is {max_horizontal_dist/units.km:.1f}km -> extending horizontal volume by {d_extent/units.km:.1f}km") + logger.info(f"95% quantile of tau decay length is {tau_95_length/units.km:.1f}km. Maximum horizontal " + f"distance for zenith range of {attributes['thetamin']/units.deg:.0f} - " + f"{attributes['thetamax']/units.deg:.0f}deg and depth of {volume['fiducial_zmin']/units.km:.1f}km " + f"is {max_horizontal_dist/units.km:.1f}km -> extending horizontal volume by {d_extent/units.km:.1f}km") rmax = d_extent + attributes['fiducial_rmax'] - if('full_zmax' in volume): - zmax = volume['full_zmax'] - else: - zmax = attributes['fiducial_zmax'] / 3. - if('full_zmin' in volume): - zmin = volume['full_zmin'] - else: - zmin = attributes['fiducial_zmin'] + + zmax = volume.get('full_zmax', attributes['fiducial_zmax'] / 3.) + zmin = volume.get('full_zmin', attributes['fiducial_zmin']) + volume_full = np.pi * (rmax ** 2 - rmin ** 2) * (zmax - zmin) # increase the total number of events such that we end up with the same number of events in the fiducial volume n_events = int(n_events * volume_full / volume_fiducial) - logger.info(f"increasing rmax from {attributes['fiducial_rmax']/units.km:.01f}km to {rmax/units.km:.01f}km, zmax from {attributes['fiducial_zmax']/units.km:.01f}km to {zmax/units.km:.01f}km") - logger.info(f"decreasing rmin from {attributes['fiducial_rmin']/units.km:.01f}km to {rmin/units.km:.01f}km") - logger.info(f"decreasing zmin from {attributes['fiducial_zmin']/units.km:.01f}km to {zmin/units.km:.01f}km") - logger.info(f"increasing number of events to {n_events:.6g}") + logger.info(f"increasing rmax from {attributes['fiducial_rmax'] / units.km:.01f}km to {rmax /units.km:.01f}km, " + f"zmax from {attributes['fiducial_zmax'] / units.km:.01f}km to {zmax / units.km:.01f}km") + + if attributes['fiducial_rmin'] != rmin: + logger.info(f"decreasing rmin from {attributes['fiducial_rmin'] / units.km:.01f}km to {rmin / units.km:.01f}km") + + if attributes['fiducial_zmin'] != zmin: + logger.info(f"decreasing zmin from {attributes['fiducial_zmin'] / units.km:.01f}km to {zmin / units.km:.01f}km") + + n_events = int(n_events * volume_full / volume_fiducial) + logger.info(f"increasing number of events to from {attributes['n_events']:.6g} {n_events:.6g}") attributes['n_events'] = n_events attributes['rmin'] = rmin @@ -492,7 +541,8 @@ def set_volume_attributes(volume, proposal, attributes): V = np.pi * (rmax ** 2 - rmin ** 2) * (zmax - zmin) attributes['volume'] = V # save full simulation volume to simplify effective volume calculation attributes['area'] = np.pi * (rmax ** 2 - rmin ** 2) - elif("fiducial_xmax" in volume): # user specifies a cube + + elif "fiducial_xmax" in volume: # user specifies a cube attributes['fiducial_xmax'] = volume['fiducial_xmax'] attributes['fiducial_xmin'] = volume['fiducial_xmin'] attributes['fiducial_ymax'] = volume['fiducial_ymax'] @@ -500,6 +550,10 @@ def set_volume_attributes(volume, proposal, attributes): attributes['fiducial_zmin'] = volume['fiducial_zmin'] attributes['fiducial_zmax'] = volume['fiducial_zmax'] + # copy keys + for key in ['fiducial_xmin', 'fiducial_xmax', 'fiducial_ymin', 'fiducial_ymax', 'fiducial_zmin', 'fiducial_zmax']: + attributes[key] = volume[key] + xmin = attributes['fiducial_xmin'] xmax = attributes['fiducial_xmax'] ymin = attributes['fiducial_ymin'] @@ -507,7 +561,8 @@ def set_volume_attributes(volume, proposal, attributes): zmin = attributes['fiducial_zmin'] zmax = attributes['fiducial_zmax'] volume_fiducial = (xmax - xmin) * (ymax - ymin) * (zmax - zmin) - if('full_xmax' in volume): + + if 'full_xmax' in volume: xmin = volume['full_xmin'] xmax = volume['full_xmax'] ymin = volume['full_ymin'] @@ -516,30 +571,41 @@ def set_volume_attributes(volume, proposal, attributes): zmax = volume['full_zmax'] # We increase the simulation volume according to the tau track length - if(proposal): + if proposal: logger.info("simulation of second interactions via PROPOSAL activated") - if('full_xmax' not in volume): # assuming that also full_xmin, full_ymin, full_ymax are not set. + if 'full_xmax' not in volume: # assuming that also full_xmin, full_ymin, full_ymax are not set. # extent fiducial by tau decay length tau_95_length = get_tau_95_length(attributes['Emax']) # check if thetamax/thetamin are in same quadrant is_same_quadrant = np.sign(np.cos(attributes['thetamin'])) == np.sign(np.cos(attributes['thetamax'])) if is_same_quadrant: - max_horizontal_dist = np.abs(max(np.abs(np.tan(attributes['thetamin'])), np.abs(np.tan(attributes['thetamax']))) * volume['fiducial_zmin']) # calculates the maximum horizontal distance through the ice + max_horizontal_dist = np.abs(max(np.abs(np.tan(attributes['thetamin'])), + np.abs(np.tan(attributes['thetamax']))) * volume['fiducial_zmin']) # calculates the maximum horizontal distance through the ice else: # not same quadrant: theta range surpasses pi/2 -> horizontal trajectories # geometric trajectory is infinite, but not acutally used. distance is limited by the min() to allowed length below max_horizontal_dist = np.inf + d_extent = min(tau_95_length, max_horizontal_dist) - logger.info(f"95% quantile of tau decay length is {tau_95_length/units.km:.1f}km. Maximum horizontal distance for zenith range of {attributes['thetamin']/units.deg:.0f} - {attributes['thetamax']/units.deg:.0f}deg and depth of {volume['fiducial_zmin']/units.km:.1f}km is {max_horizontal_dist/units.km:.1f}km -> extending horizontal volume by {d_extent/units.km:.1f}km") + logger.info(f"95% quantile of tau decay length is {tau_95_length/units.km:.1f}km. Maximum horizontal distance for zenith range of " + f"{attributes['thetamin']/units.deg:.0f} - {attributes['thetamax']/units.deg:.0f}deg and depth of " + f"{volume['fiducial_zmin']/units.km:.1f}km is {max_horizontal_dist/units.km:.1f}km -> extending horizontal volume by {d_extent/units.km:.1f}km") + xmax += d_extent xmin -= d_extent ymax += d_extent ymin -= d_extent - logger.info(f"increasing xmax from {attributes['fiducial_xmax']/units.km:.01f}km to {xmax/units.km:.01f}km, ymax from {attributes['fiducial_ymax']/units.km:.01f}km to {ymax/units.km:.01f}km, zmax from {attributes['fiducial_zmax']/units.km:.01f}km to {zmax/units.km:.01f}km") - logger.info(f"decreasing xmin from {attributes['fiducial_xmin']/units.km:.01f}km to {xmin/units.km:.01f}km, ymin from {attributes['fiducial_ymin']/units.km:.01f}km to {ymin/units.km:.01f}km") - logger.info(f"decreasing zmin from {attributes['fiducial_zmin']/units.km:.01f}km to {zmin/units.km:.01f}km") + + logger.info(f"increasing xmax from {attributes['fiducial_xmax'] / units.km:.01f}km to {xmax / units.km:.01f}km, " + f"ymax from {attributes['fiducial_ymax'] / units.km:.01f}km to {ymax / units.km:.01f}km, " + f"zmax from {attributes['fiducial_zmax'] / units.km:.01f}km to {zmax / units.km:.01f}km") + + logger.info(f"decreasing xmin from {attributes['fiducial_xmin'] / units.km:.01f}km to " + f"{xmin / units.km:.01f}km, ymin from {attributes['fiducial_ymin'] / units.km:.01f}km to {ymin / units.km:.01f}km") + logger.info(f"decreasing zmin from {attributes['fiducial_zmin'] / units.km:.01f}km to {zmin / units.km:.01f}km") volume_full = (xmax - xmin) * (ymax - ymin) * (zmax - zmin) + n_events = int(n_events * volume_full / volume_fiducial) logger.info(f"increasing number of events from {attributes['n_events']} to {n_events:.6g}") attributes['n_events'] = n_events @@ -555,38 +621,40 @@ def set_volume_attributes(volume, proposal, attributes): attributes['volume'] = V # save full simulation volume to simplify effective volume calculation attributes['area'] = (xmax - xmin) * (ymax - ymin) else: - raise AttributeError(f"'fiducial_rmin' or 'fiducial_rmax' is not part of 'volume'") + raise AttributeError(f"'fiducial_xmax' or 'fiducial_rmax' is not part of 'volume'. Can not define a volume") def generate_vertex_positions(attributes, n_events, rnd=None): """ - helper function that generates the vertex position randomly distributed in simulation volume. - The relevant quantities are also saved into the hdf5 attributes + Helper function that generates the vertex position randomly distributed in simulation volume. + The relevant quantities are also saved into the hdf5 attributes. Parameters ---------- attributes: dicitionary dict storing hdf5 attributes + rnd: random generator object if None is provided, a new default random generator object is initialized """ - if(rnd is None): + if rnd is None: rnd = np.random.default_rng() - if("fiducial_rmax" in attributes): # user specifies a cylinder + if "fiducial_rmax" in attributes: # user specifies a cylinder rr_full = rnd.uniform(attributes['rmin'] ** 2, attributes['rmax'] ** 2, n_events) ** 0.5 phiphi = rnd.uniform(0, 2 * np.pi, n_events) xx = rr_full * np.cos(phiphi) yy = rr_full * np.sin(phiphi) zz = rnd.uniform(attributes['zmin'], attributes['zmax'], n_events) - return xx, yy, zz - elif("fiducial_xmax" in attributes): # user specifies a cube + + elif "fiducial_xmax" in attributes: # user specifies a cube xx = rnd.uniform(attributes['xmin'], attributes['xmax'], n_events) yy = rnd.uniform(attributes['ymin'], attributes['ymax'], n_events) zz = rnd.uniform(attributes['zmin'], attributes['zmax'], n_events) - return xx, yy, zz + else: - raise AttributeError(f"'fiducial_rmin' or 'fiducial_rmax' is not part of 'attributes'") + raise AttributeError(f"'fiducial_xmax' or 'fiducial_rmax' is not part of 'attributes'") + return xx + attributes["x0"], yy + attributes["y0"], zz def intersection_box_ray(bounds, ray): """ @@ -731,53 +799,7 @@ def generate_surface_muons(filename, n_events, Emin, Emax, the maximum neutrino energy (energies are randomly chosen assuming a uniform distribution in the logarithm of the energy) volume: dict - a dictionary specifying the simulation volume - can be either a cylinder spefified via the keys - - * fiducial_rmin: float - lower r coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_rmax: float - upper r coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmin: float - lower z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmax: float - upper z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * full_rmin: float (optional) - lower r coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_rmax: float (optional) - upper r coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_zmin: float (optional) - lower z coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_zmax: float (optional) - upper z coordinate of simulated volume (if not set it is set to the fiducial volume) - - or a cube specified with - - * fiducial_xmin: float - lower x coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_xmax: float - upper x coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_ymin: float - lower y coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_ymax: float - upper y coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmin: float - lower z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmax: float - upper z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * full_xmin: float (optional) - lower x coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_xmax: float (optional) - upper x coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_ymin: float (optional) - lower y coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_ymax: float (optional) - upper y coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_zmin: float (optional) - lower z coordinate of simulated volume (if not set it is set to the fiducial volume) - * full_zmax: float (optional) - upper z coordinate of simulated volume (if not set it is set to the fiducial volume) - + a dictionary specifying the simulation volume. See set_volume_attributes() for an explanation thetamin: float lower zenith angle for neutrino arrival direction thetamax: float @@ -804,7 +826,7 @@ def generate_surface_muons(filename, n_events, Emin, Emax, * 'log_uniform': uniformly distributed in the logarithm of energy * 'E-?': E to the -? spectrum where ? can be any float * 'IceCube-nu-2017': astrophysical neutrino flux measured with IceCube muon sample (https://doi.org/10.22323/1.301.1005) - * 'GZK-1': GZK neutrino flux model from van Vliet et al., 2019, https://arxiv.org/abs/1901.01899v1 + * 'GZK-1': GZK neutrino flux model from van Vliet et al., 2019, https://arxiv.org/abs/1901.01899v1 for 10% proton fraction (see get_proton_10 in examples/Sensitivities/E2_fluxes3.py for details) * 'GZK-1+IceCube-nu-2017': a combination of the cosmogenic (GZK-1) and astrophysical (IceCube nu 2017) flux @@ -1044,53 +1066,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, Emax: float the maximum neutrino energy volume: dict - a dictionary specifying the simulation volume - can be either a cylinder spefified via the keys - - * fiducial_rmin: float - lower r coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_rmax: float - upper r coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmin: float - lower z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmax: float - upper z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * full_rmin: float (optional) - lower r coordinate of simulated volume (if not set it is set to 1/3 of the fiducial volume, if second vertices are not activated it is set to the fiducial volume) - * full_rmax: float (optional) - upper r coordinate of simulated volume (if not set it is set to the fiducial volume + the 95% quantile of the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_zmin: float (optional) - lower z coordinate of simulated volume (if not set it is set to the fiducial volume - the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_zmax: float (optional) - upper z coordinate of simulated volume (if not set it is set to 1/3 of the fiducial volume , if second vertices are not activated it is set to the fiducial volume) - - or a cube specified with - - * fiducial_xmin: float - lower x coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_xmax: float - upper x coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_ymin: float - lower y coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_ymax: float - upper y coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmin: float - lower z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * fiducial_zmax: float - upper z coordinate of fiducial volume (the fiducial volume needs to be chosen large enough such that no events outside of it will trigger) - * full_xmin: float (optional) - lower x coordinate of simulated volume (if not set it is set to the fiducial volume - the 95% quantile of the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_xmax: float (optional) - upper x coordinate of simulated volume (if not set it is set to the fiducial volume + the 95% quantile of the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_ymin: float (optional) - lower y coordinate of simulated volume (if not set it is set to the fiducial volume - the 95% quantile of the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_ymax: float (optional) - upper y coordinate of simulated volume (if not set it is set to the fiducial volume + the 95% quantile of the tau decay length, if second vertices are not activated it is set to the fiducial volume) - * full_zmin: float (optional) - lower z coordinate of simulated volume (if not set it is set to 1/3 of the fiducial volume, if second vertices are not activated it is set to the fiducial volume) - * full_zmax: float (optional) - upper z coordinate of simulated volume (if not set it is set to the fiducial volume - the tau decay length, if second vertices are not activated it is set to the fiducial volume) - + a dictionary specifying the simulation volume. See set_volume_attributes() for an explanation thetamin: float lower zenith angle for neutrino arrival direction (default 0deg) thetamax: float @@ -1127,7 +1103,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, * 'E-?': E to the -? spectrum where ? can be any float * 'IceCube-nu-2017': astrophysical neutrino flux measured with IceCube muon sample (https://doi.org/10.22323/1.301.1005) * 'IceCube-nu-2022': astrophysical neutrino flux measured with IceCube muon sample (https://doi.org/10.48550/arXiv.2111.10299) - * 'GZK-1': GZK neutrino flux model from van Vliet et al., 2019, https://arxiv.org/abs/1901.01899v1 + * 'GZK-1': GZK neutrino flux model from van Vliet et al., 2019, https://arxiv.org/abs/1901.01899v1 for 10% proton fraction (see get_proton_10 in examples/Sensitivities/E2_fluxes3.py for details) * 'GZK-2': GZK neutrino flux model from fit to TA data (https://pos.sissa.it/395/338/) * 'GZK-1+IceCube-nu-2017': a combination of the cosmogenic (GZK-1) and astrophysical (IceCube nu 2017) flux @@ -1172,17 +1148,17 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, """ rnd = Generator(Philox(seed)) t_start = time.time() - + if log_level is not None: logger.setLevel(log_level) - + if proposal: from NuRadioMC.EvtGen.NuRadioProposal import ProposalFunctions proposal_functions = ProposalFunctions(config_file=proposal_config, tables_path=proposal_tables_path) max_n_events_batch = int(max_n_events_batch) attributes = {} n_events = int(n_events) - + # sanity check for f in flavor: if f not in [12, -12, 14, -14, 16, -16]: @@ -1214,10 +1190,10 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, n_batches = int(np.ceil(n_events / max_n_events_batch)) for i_batch in range(n_batches): # do generation of events in batches n_events_batch = max_n_events_batch - + if i_batch + 1 == n_batches: # last batch? n_events_batch = n_events - (i_batch * max_n_events_batch) - + logger.info(f"processing batch {i_batch+1:.2g}/{n_batches:.2g} with {n_events_batch:.2g} events") data_sets["xx"], data_sets["yy"], data_sets["zz"] = generate_vertex_positions(attributes=attributes, n_events=n_events_batch, rnd=rnd) @@ -1244,10 +1220,10 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, data_sets["interaction_type"] = inelasticities.get_ccnc(n_events_batch, rnd=rnd) elif interaction_type == "cc" or interaction_type == "nc": data_sets["interaction_type"] = np.full(n_events_batch, interaction_type, dtype='U2') - else: + else: logger.error(f"Input illegal interaction type: {interaction_type}") raise ValueError(f"Input illegal interaction type: {interaction_type}") - + # generate inelasticity logger.debug("generating inelasticities") data_sets["inelasticity"] = inelasticities.get_neutrino_inelasticity(n_events_batch, rnd=rnd) @@ -1268,7 +1244,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, em_shower_mask = (data_sets["interaction_type"] == "cc") & (np.abs(data_sets['flavors']) == 12) # transform datatype to list so that inserting elements is faster - for key in data_sets: + for key in data_sets: data_sets[key] = list(data_sets[key]) # loop over all events where an EM shower needs to be inserted @@ -1304,7 +1280,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, E_all_leptons = (1 - data_sets["inelasticity"]) * data_sets["energies"] lepton_codes = copy.copy(data_sets["flavors"]) - + # convert neutrino flavors (makes only sense for cc interaction which is ensured with "mask_leptons") lepton_codes = lepton_codes - 1 * np.sign(lepton_codes) @@ -1324,7 +1300,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, x_nu = data_sets['xx'][iE] y_nu = data_sets['yy'][iE] z_nu = data_sets['zz'][iE] - + # Appending event if it interacts within the fiducial volume if is_in_fiducial_volume(attributes, np.array([x_nu, y_nu, z_nu])): for key in iterkeys(data_sets): @@ -1335,13 +1311,13 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, if mask_tracks[iE]: geometry_selection = get_intersection_volume_neutrino( attributes, [x_nu, y_nu, z_nu], lepton_directions[iE]) - + if geometry_selection: products_array = proposal_functions.get_secondaries_array( np.array([E_all_leptons[iE]]), np.array([lepton_codes[iE]]), np.array([lepton_positions[iE]]), np.array([lepton_directions[iE]]), **proposal_kwargs) - + products = products_array[0] n_interaction = 2 for product in products: @@ -1364,14 +1340,14 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, data_sets_fiducial['n_interaction'][-1] = n_interaction # specify that new event is a secondary interaction n_interaction += 1 - + # store energy of parent lepton before producing the shower data_sets_fiducial['energies'][-1] = product.parent_energy data_sets_fiducial['shower_energies'][-1] = product.energy data_sets_fiducial['inelasticity'][-1] = np.nan - + # For neutrino interactions 'interaction_type' contains 'cc' or 'nc' - # For energy losses of leptons use name of produced particle + # For energy losses of leptons use name of produced particle data_sets_fiducial['interaction_type'][-1] = particle_names.particle_name(product.code) data_sets_fiducial['shower_type'][-1] = product.shower_type @@ -1384,7 +1360,7 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, # Store flavor/particle code of parent particle data_sets_fiducial['flavors'][-1] = lepton_codes[iE] - + time_proposal = time.time() - init_time else: if(n_batches == 1): From 8bf56a0f2a8ae02daa4417b8858742105640f785 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Sun, 17 Dec 2023 20:07:55 +0100 Subject: [PATCH 32/54] added warning if multiple triggers specify pre_trigger_times --- NuRadioReco/modules/triggerTimeAdjuster.py | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index fc2a79a57..29edb6a2c 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -26,8 +26,8 @@ def begin(self, trigger_name=None, pre_trigger_time=55. * units.ns): ---------- trigger_name: string or None name of the trigger that should be used. - If trigger_name is None, the trigger with the smalles trigger_time will be used. - If a name is give, corresponding trigger module must be run beforehand. + If trigger_name is None, the trigger with the smallest trigger_time will be used. + If a name is given, corresponding trigger module must be run beforehand. If the trigger does not exist or did not trigger, this module will do nothing pre_trigger_time: float or dict Amount of time that should be stored in the channel trace before the trigger. @@ -64,8 +64,11 @@ def run(self, event, station, detector, mode='sim_to_data'): mode: 'sim_to_data' (default) | 'data_to_sim' If 'sim_to_data', cuts the (arbitrary-length) simulated traces to the appropriate readout windows. If 'data_to_sim', - looks through all triggers in the station and adjusts the + looks through all triggers in the station and adjusts the trace_start_time according to the different readout delays + + If the ``trigger_name`` was specified in the ``begin`` function, + only this trigger is considered. """ if mode == 'sim_to_data': @@ -116,7 +119,7 @@ def run(self, event, station, detector, mode='sim_to_data'): else: logger.error( 'pre_trigger_time was specified as a dictionary, ' - f'but the neither the trigger_name {trigger_name} ' + f'but neither the trigger_name {trigger_name} ' f'nor the channel id {channel_id} are present as keys' ) raise KeyError @@ -156,8 +159,19 @@ def run(self, event, station, detector, mode='sim_to_data'): else: logger.debug('Trigger {} has not triggered. Channel timings will not be changed.'.format(self.__trigger_name)) elif mode == 'data_to_sim': - for trigger in station.get_triggers().values(): - pre_trigger_time = trigger.get_pre_trigger_times() + if self.__trigger_name is not None: + triggers = [station.get_trigger(self.__trigger_name)] + else: + triggers = station.get_triggers().values() + + pre_trigger_times = [trigger.get_pre_trigger_times() for trigger in triggers] + if np.sum([dt is not None for dt in pre_trigger_times]) > 1: + logger.warning( + 'More than one trigger claims to have adjusted the pre_trigger_times. ' + 'Normally, only one trigger should set pre_trigger_times. ' + ) + + for pre_trigger_time in pre_trigger_times: if pre_trigger_time is not None: for channel in station.iter_channels(): channel.set_trace_start_time(channel.get_trace_start_time()-pre_trigger_time[channel.get_id()]) From 5e12b3ed8c12f5ffef98ccc5f224bbd13cfc7a50 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Sun, 17 Dec 2023 22:52:03 +0100 Subject: [PATCH 33/54] raise warning if triggerTimeAdjuster is called before resampling to detector sampling rate --- NuRadioReco/modules/triggerTimeAdjuster.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index 29edb6a2c..a8e10c655 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -42,6 +42,7 @@ def begin(self, trigger_name=None, pre_trigger_time=55. * units.ns): """ self.__trigger_name = trigger_name self.__pre_trigger_time = pre_trigger_time + self.__sampling_rate_warning_issued = False @register_run() def run(self, event, station, detector, mode='sim_to_data'): @@ -95,10 +96,11 @@ def run(self, event, station, detector, mode='sim_to_data'): trace = channel.get_trace() trace_length = len(trace) + detector_sampling_rate = detector.get_sampling_frequency(station.get_id(), channel.get_id()) number_of_samples = int( 2 * np.ceil( # this should ensure that 1) the number of samples is even and 2) resampling to the detector sampling rate results in the correct number of samples (note that 2) can only be guaranteed if the detector sampling rate is lower than the current sampling rate) - detector.get_number_of_samples(station.get_id(), channel.get_id()) / 2 - * channel.get_sampling_rate() / detector.get_sampling_frequency(station.get_id(), channel.get_id()) + detector.get_number_of_samples(station.get_id(), channel.get_id()) / 2 + * channel.get_sampling_rate() / detector_sampling_rate )) if number_of_samples > trace.shape[0]: logger.error("Input has fewer samples than desired output. Channels has only {} samples but {} samples are requested.".format( @@ -106,6 +108,7 @@ def run(self, event, station, detector, mode='sim_to_data'): raise AttributeError else: sampling_rate = channel.get_sampling_rate() + self.__check_sampling_rates(detector_sampling_rate, sampling_rate) trigger_time_sample = int(np.round(trigger_time_channel * sampling_rate)) # logger.debug(f"channel {channel.get_id()}: trace_start_time = {channel.get_trace_start_time():.1f}ns, trigger time channel {trigger_time_channel/units.ns:.1f}ns, trigger time sample = {trigger_time_sample}") pre_trigger_time = self.__pre_trigger_time @@ -177,3 +180,14 @@ def run(self, event, station, detector, mode='sim_to_data'): channel.set_trace_start_time(channel.get_trace_start_time()-pre_trigger_time[channel.get_id()]) else: raise ValueError(f"Argument '{mode}' for mode is not valid. Options are 'sim_to_data' or 'data_to_sim'.") + + def __check_sampling_rates(self, detector_sampling_rate, channel_sampling_rate): + if not self.__sampling_rate_warning_issued: # we only issue this warning once + if not np.isclose(detector_sampling_rate, channel_sampling_rate): + logger.warning( + 'triggerTimeAdjuster was called, but the channel sampling rate ' + f'({channel_sampling_rate/units.GHz:3.f} GHz) is not equal to ' + f'the target detector sampling rate ({detector_sampling_rate/units.GHz:.3f} GHz). ' + 'Traces may not have the correct trace length after resampling.' + ) + self.__sampling_rate_warning_issued = True From c09394945cd6d57f5518dcdc37c32b74e34800b4 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Sun, 17 Dec 2023 22:52:51 +0100 Subject: [PATCH 34/54] enable triggerTimeAdjuster logger inheritance from NuRadioReco logger --- NuRadioReco/modules/triggerTimeAdjuster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index a8e10c655..6560e6395 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -3,7 +3,7 @@ import logging from NuRadioReco.utilities import units -logger = logging.getLogger('triggerTimeAdjuster') +logger = logging.getLogger('NuRadioReco.triggerTimeAdjuster') class triggerTimeAdjuster: From 5e8e136a64c66a10ce7710ff159a407716521779 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Mon, 18 Dec 2023 07:41:26 +0100 Subject: [PATCH 35/54] fix typo in f-string --- NuRadioReco/modules/triggerTimeAdjuster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioReco/modules/triggerTimeAdjuster.py b/NuRadioReco/modules/triggerTimeAdjuster.py index 6560e6395..57cb52399 100644 --- a/NuRadioReco/modules/triggerTimeAdjuster.py +++ b/NuRadioReco/modules/triggerTimeAdjuster.py @@ -186,7 +186,7 @@ def __check_sampling_rates(self, detector_sampling_rate, channel_sampling_rate): if not np.isclose(detector_sampling_rate, channel_sampling_rate): logger.warning( 'triggerTimeAdjuster was called, but the channel sampling rate ' - f'({channel_sampling_rate/units.GHz:3.f} GHz) is not equal to ' + f'({channel_sampling_rate/units.GHz:.3f} GHz) is not equal to ' f'the target detector sampling rate ({detector_sampling_rate/units.GHz:.3f} GHz). ' 'Traces may not have the correct trace length after resampling.' ) From 4c7ea36adab18f1a0e2df3ae6810d6bd15b30112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Mon, 18 Dec 2023 11:53:00 +0100 Subject: [PATCH 36/54] Modify _is_in_fiducial_volume in simulation.py. Take into account shifted fiducial volume --- NuRadioMC/simulation/simulation.py | 42 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 42a1d19fa..22c320adf 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -591,8 +591,9 @@ def run(self): # skip vertices not in fiducial volume. This is required because 'mother' events are added to the event list # if daugthers (e.g. tau decay) have their vertex in the fiducial volume - if not self._is_in_fiducial_volume(): - logger.debug(f"event is not in fiducial volume, skipping simulation {self._fin['xx'][self._shower_index]}, {self._fin['yy'][self._shower_index]}, {self._fin['zz'][self._shower_index]}") + if not self._is_in_fiducial_volume(self._shower_vertex): + logger.debug(f"event is not in fiducial volume, skipping simulation {self._fin['xx'][self._shower_index]}, " + f"{self._fin['yy'][self._shower_index]}, {self._fin['zz'][self._shower_index]}") continue # for special cases where only EM or HAD showers are simulated, skip all events that don't fulfill this criterion @@ -1063,26 +1064,27 @@ def _is_simulate_noise(self): """ return bool(self._cfg['noise']) - def _is_in_fiducial_volume(self): - """ - checks wether a vertex is in the fiducial volume + def _is_in_fiducial_volume(self, pos): + """ Checks if pos is in fiducial volume """ - if the fiducial volume is not specified in the input file, True is returned (this is required for the simulation - of pulser calibration measuremens) - """ - tt = ['fiducial_rmin', 'fiducial_rmax', 'fiducial_zmin', 'fiducial_zmax'] - has_fiducial = True - for t in tt: - if not t in self._fin_attrs: - has_fiducial = False - if not has_fiducial: - return True - - r = (self._shower_vertex[0] ** 2 + self._shower_vertex[1] ** 2) ** 0.5 - if r >= self._fin_attrs['fiducial_rmin'] and r <= self._fin_attrs['fiducial_rmax']: - if self._shower_vertex[2] >= self._fin_attrs['fiducial_zmin'] and self._shower_vertex[2] <= self._fin_attrs['fiducial_zmax']: + for check_attr in ['fiducial_zmin', 'fiducial_zmax']: + if not check_attr in self._fin_attrs: + self.logger.warning("Fiducial volume not defined. Return True") return True - return False + + pos = copy.deepcopy(pos) - np.array([self._fin_attrs["x0"], self._fin_attrs["y0"], 0]) + + if not (self._fin_attrs["fiducial_zmin"] < pos[2] < self._fin_attrs["fiducial_zmax"]): + return False + + if "fiducial_rmax" in self._fin_attrs: + radius = np.sqrt(pos[0] ** 2 + pos[1] ** 2) + return self._fin_attrs["fiducial_rmin"] < radius < self._fin_attrs["fiducial_rmax"] + elif "fiducial_xmax" in self._fin_attrs: + return (self._fin_attrs["fiducial_xmin"] < pos[0] < self._fin_attrs["fiducial_xmax"] and + self._fin_attrs["fiducial_ymin"] < pos[1] < self._fin_attrs["fiducial_ymax"]) + else: + raise ValueError("Could not contruct fiducial volume from input file.") def _increase_signal(self, channel_id, factor): """ From 7dcbba6a082495f00477d885829be7206851a540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Mon, 18 Dec 2023 11:55:14 +0100 Subject: [PATCH 37/54] Use dict.get and 0 as default for backwards compatibility --- NuRadioMC/simulation/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 22c320adf..801f5e213 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -1072,7 +1072,7 @@ def _is_in_fiducial_volume(self, pos): self.logger.warning("Fiducial volume not defined. Return True") return True - pos = copy.deepcopy(pos) - np.array([self._fin_attrs["x0"], self._fin_attrs["y0"], 0]) + pos = copy.deepcopy(pos) - np.array([self._fin_attrs.get("x0", 0), self._fin_attrs.get("y0", 0), 0]) if not (self._fin_attrs["fiducial_zmin"] < pos[2] < self._fin_attrs["fiducial_zmax"]): return False From 139e0cd64ad0b6cf79561e5b9f42d7bb1ba1bcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Mon, 18 Dec 2023 19:08:57 +0100 Subject: [PATCH 38/54] Improve logging --- NuRadioMC/simulation/simulation.py | 71 ++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 7d983ba36..469f6e120 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -325,7 +325,7 @@ def __init__(self, inputfilename, integrated_channel_response = np.trapz(np.abs(filt) ** 2, ff) self._integrated_channel_response[self._station_id][channel_id] = integrated_channel_response - logger.debug(f"Estimated bandwidth of station {self._station_id} channel {channel_id} is " + logger.debug(f"Station.channel {self._station_id}.{channel_id} estimated bandwidth is " f"{integrated_channel_response / mean_integrated_response / units.MHz:.1f} MHz") ################################ @@ -338,54 +338,79 @@ def __init__(self, inputfilename, Vrms = self._cfg['trigger']['Vrms'] if noise_temp is not None and Vrms is not None: - raise AttributeError(f"Specifying noise temperature (set to {noise_temp}) and Vrms (set to {Vrms} is not allowed.") + raise AttributeError(f"Specifying noise temperature (set to {noise_temp}) and Vrms (set to {Vrms}) is not allowed).") + + self._Vrms_per_channel = collections.defaultdict(dict) + self._Vrms_efield_per_channel = collections.defaultdict(dict) if noise_temp is not None: + if noise_temp == "detector": + logger.status("Use noise temperature from detector description to determine noise Vrms in each channel.") self._noise_temp = None # the noise temperature is defined in the detector description else: self._noise_temp = float(noise_temp) + logger.status(f"Use a noise temperature of {noise_temp / units.kelvin:.1f} K for each channel to determine noise Vrms.") - self._Vrms_per_channel = {} - self._noiseless_channels = {} + self._noiseless_channels = collections.defaultdict(dict) for station_id in self._integrated_channel_response: - self._Vrms_per_channel[station_id] = {} - self._noiseless_channels[station_id] = [] + for channel_id in self._integrated_channel_response[station_id]: if self._noise_temp is None: noise_temp_channel = self._det.get_noise_temperature(station_id, channel_id) else: noise_temp_channel = self._noise_temp + if self._det.is_channel_noiseless(station_id, channel_id): self._noiseless_channels[station_id].append(channel_id) - # from elog:1566 and https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise (last Eq. in "noise voltage and power" section - self._Vrms_per_channel[station_id][channel_id] = (noise_temp_channel * 50 * constants.k * - self._integrated_channel_response[station_id][channel_id] / units.Hz) ** 0.5 - logger.debug(f'station {station_id} channel {channel_id} noise temperature = {noise_temp_channel} K -> Vrms = ' - f'{self._Vrms_per_channel[station_id][channel_id]/ units.mV:.2f} mV') + # Calculation of Vrms. For details see from elog:1566 and https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise + # (last two Eqs. in "noise voltage and power" section) or our wiki https://nu-radio.github.io/NuRadioMC/NuRadioMC/pages/HDF5_structure.html + + # Bandwidth, i.e., \Delta f in equation + integrated_channel_response = self._integrated_channel_response[station_id][channel_id] + max_amplification = self._max_amplification_per_channel[station_id][channel_id] + + self._Vrms_per_channel[station_id][channel_id] = (noise_temp_channel * 50 * constants.k * integrated_channel_response / units.Hz) ** 0.5 + self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms_per_channel[station_id][channel_id] / max_amplification / units.m # VEL = 1m + + # for logging + mean_integrated_response = self._integrated_channel_response_normalization[self._station_id][channel_id] + + logger.status(f'Station.channel {station_id}.{channel_id:02d}: noise temperature = {noise_temp_channel} K, ' + f'est. bandwidth = {integrated_channel_response / mean_integrated_response / units.MHz:.2f} MHz, ' + f'max. filter amplification = {max_amplification:.2e} -> Vrms = ' + f'{self._Vrms_per_channel[station_id][channel_id] / units.mV:.2f} mV -> efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (VEL = 1m) ') self._Vrms = next(iter(next(iter(self._Vrms_per_channel.values())).values())) - logger.status('noise temperature = {}, est. bandwidth = {:.2f} MHz -> Vrms = {:.2f} muV (for station {:d} and channel {:d})'.format( - self._noise_temp, self._bandwidth / norm / units.MHz, self._Vrms / units.V / units.micro, self._station_ids[0], 0)) elif Vrms is not None: self._Vrms = float(Vrms) * units.V self._noise_temp = None + logger.status(f"Use a fix noise Vrms of {self._Vrms / units.mV:.2f} mV in each channel.") + + for station_id in self._integrated_channel_response: + + for channel_id in self._integrated_channel_response[station_id]: + max_amplification = self._max_amplification_per_channel[station_id][channel_id] + self._Vrms_per_channel[station_id][channel_id] = self._Vrms # to be stored in the hdf5 file + self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms / max_amplification / units.m # VEL = 1m + + # for logging + integrated_channel_response = self._integrated_channel_response[station_id][channel_id] + mean_integrated_response = self._integrated_channel_response_normalization[self._station_id][channel_id] + + logger.status(f'Station.channel {station_id}.{channel_id:02d}: ' + f'est. bandwidth = {integrated_channel_response / mean_integrated_response / units.MHz:.2f} MHz, ' + f'max. filter amplification = {max_amplification:.2e} -> ' + f'efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (VEL = 1m) ') + else: raise AttributeError(f"noise temperature and Vrms are both set to None") - self._Vrms_efield_per_channel = {} - for station_id in self._integrated_channel_response: - self._Vrms_efield_per_channel[station_id] = {} - for channel_id in self._integrated_channel_response[station_id]: - self._Vrms_efield_per_channel[station_id][channel_id] = self._Vrms_per_channel[station_id][channel_id] / self._max_amplification_per_channel[station_id][channel_id] / units.m - self._Vrms_efield = next(iter(next(iter(self._Vrms_efield_per_channel.values())).values())) - tmp_cut = float(self._cfg['speedup']['min_efield_amplitude']) - logger.status(f"final Vrms {self._Vrms/units.V:.2g}V corresponds to an efield of {self._Vrms_efield/units.V/units.m/units.micro:.2g} " - f"muV/m for a VEL = 1m (amplification factor of system is {amplification:.1f}).\n -> all signals with less then " - f"{tmp_cut:.1f} x Vrms_efield = {tmp_cut * self._Vrms_efield/units.m/units.V/units.micro:.2g}muV/m will be skipped") + speed_cut = float(self._cfg['speedup']['min_efield_amplitude']) + logger.status(f"All signals with less then {speed_cut:.1f} x Vrms_efield will be skipped.") self._distance_cut_polynomial = None if self._cfg['speedup']['distance_cut']: From 9b6b32a8e7ba1a48ddaf066b87e374d952cf7def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= <30903175+fschlueter@users.noreply.github.com> Date: Tue, 19 Dec 2023 10:57:47 +0100 Subject: [PATCH 39/54] Update simulation.py: self.logger -> logger --- NuRadioMC/simulation/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 801f5e213..a48a18850 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -1069,7 +1069,7 @@ def _is_in_fiducial_volume(self, pos): for check_attr in ['fiducial_zmin', 'fiducial_zmax']: if not check_attr in self._fin_attrs: - self.logger.warning("Fiducial volume not defined. Return True") + logger.warning("Fiducial volume not defined. Return True") return True pos = copy.deepcopy(pos) - np.array([self._fin_attrs.get("x0", 0), self._fin_attrs.get("y0", 0), 0]) From 97e55cd383af34b81ab013c7a0887161ec1224f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Wed, 20 Dec 2023 13:25:07 +0100 Subject: [PATCH 40/54] Only remove whitespace. Often from my code. --- NuRadioReco/detector/detector_base.py | 36 ++--- NuRadioReco/framework/base_shower.py | 16 +-- NuRadioReco/framework/base_station.py | 36 ++--- NuRadioReco/framework/electric_field.py | 2 +- NuRadioReco/framework/event.py | 28 ++-- NuRadioReco/framework/parameters.py | 4 +- NuRadioReco/framework/particle.py | 10 +- ...fieldRadioInterferometricReconstruction.py | 130 +++++++++--------- NuRadioReco/utilities/interferometry.py | 30 ++-- 9 files changed, 148 insertions(+), 144 deletions(-) diff --git a/NuRadioReco/detector/detector_base.py b/NuRadioReco/detector/detector_base.py index 8d28b0f8d..fd4e2ffd4 100644 --- a/NuRadioReco/detector/detector_base.py +++ b/NuRadioReco/detector/detector_base.py @@ -140,7 +140,7 @@ def __init__(self, source='json', json_filename='ARIANNA/arianna_detector_db.jso is returned. To force the creation of a new detector instance, pass the additional keyword parameter `create_new=True` to this function. For more details, check the documentation for the `Singleton metaclass `_. - + Parameters ---------- source : str @@ -231,7 +231,7 @@ def _query_channels(self, station_id): return self._channels.search((Channel.station_id == station_id) & (Channel.commission_time <= self.__current_time.datetime) & (Channel.decommission_time > self.__current_time.datetime)) - + def _query_devices(self, station_id): Device = Query() if self.__current_time is None: @@ -305,13 +305,13 @@ def __get_channel(self, station_id, channel_id): if station_id not in self._buffered_stations.keys(): self._buffer(station_id) return self._buffered_channels[station_id][channel_id] - - + + def __get_devices(self, station_id): if station_id not in self._buffered_stations.keys(): self._buffer(station_id) return self._buffered_devices[station_id] - + def __get_device(self, station_id, device_id): if station_id not in self._buffered_stations.keys(): self._buffer(station_id) @@ -333,7 +333,7 @@ def _buffer(self, station_id): self._buffered_devices[station_id][device['device_id']] = device self.__valid_t0 = max(self.__valid_t0, astropy.time.Time(channel['commission_time'])) self.__valid_t1 = min(self.__valid_t1, astropy.time.Time(channel['decommission_time'])) - + def __buffer_position(self, position_id): self.__buffered_positions[position_id] = self.__query_position(position_id) @@ -411,6 +411,7 @@ def update(self, time): self.__current_time = astropy.time.Time(time) else: self.__current_time = time + logger.info("updating detector time to {}".format(self.__current_time)) if not ((self.__current_time > self.__valid_t0) and (self.__current_time < self.__valid_t1)): self._buffered_stations = {} @@ -440,8 +441,8 @@ def get_channel(self, station_id, channel_id): dict of channel parameters """ return self.__get_channel(station_id, channel_id) - - + + def get_device(self, station_id, device_id): """ returns a dictionary of all device parameters @@ -530,11 +531,14 @@ def get_relative_position(self, station_id, channel_id, mode = 'channel'): ------- 3-dim array of relative station position """ - if mode == 'channel': res = self.__get_channel(station_id, channel_id) - elif mode == 'device': res = self.__get_device(station_id, channel_id) - else: + if mode == 'channel': + res = self.__get_channel(station_id, channel_id) + elif mode == 'device': + res = self.__get_device(station_id, channel_id) + else: logger.error("Mode {} does not exist. Use 'channel' or 'device'".format(mode)) raise NameError + return np.array([res['ant_position_x'], res['ant_position_y'], res['ant_position_z']]) def get_site(self, station_id): @@ -639,10 +643,10 @@ def get_parallel_channels(self, station_id): if np.sum(mask): parallel_antennas.append(channel_ids[mask]) return np.array(parallel_antennas) - - - - + + + + def get_number_of_devices(self, station_id): """ Get the number of devices per station @@ -921,7 +925,7 @@ def get_noise_RMS(self, station_id, channel_id, stage='amp'): stage: string (default 'amp') specifies the stage of reconstruction you want the noise RMS for, `stage` can be one of - + * 'raw' (raw measured trace) * 'amp' (after the amp was deconvolved) * 'filt' (after the trace was highpass with 100MHz diff --git a/NuRadioReco/framework/base_shower.py b/NuRadioReco/framework/base_shower.py index 754930f4b..9d6af2cad 100644 --- a/NuRadioReco/framework/base_shower.py +++ b/NuRadioReco/framework/base_shower.py @@ -43,17 +43,17 @@ def has_parameter(self, key): def get_axis(self): """ - Returns the (shower) axis. - + Returns the (shower) axis. + The axis is antiparallel to the movement of the shower particla and point towards the origin of the shower. Returns ------- - np.array(3,) + np.array(3,) Shower axis - + """ if not self.has_parameter(parameters.showerParameters.azimuth) or \ not self.has_parameter(parameters.showerParameters.zenith): @@ -66,18 +66,18 @@ def get_axis(self): self.get_parameter(parameters.showerParameters.azimuth)) def get_coordinatesystem(self): - """ + """ Returns radiotools.coordinatesystem.cstrafo for shower geometry. - + Can be used to transform the radio pulses or the observer coordiates - in the shower frame. Requieres the shower arrival direction + in the shower frame. Requieres the shower arrival direction (azimuth and zenith angle) and magnetic field vector (parameters.showerParameters). Returns ------- radiotools.coordinatesystem.cstrafo - + """ if not self.has_parameter(parameters.showerParameters.azimuth) or \ not self.has_parameter(parameters.showerParameters.zenith) or \ diff --git a/NuRadioReco/framework/base_station.py b/NuRadioReco/framework/base_station.py index d1d671e9b..18730600c 100644 --- a/NuRadioReco/framework/base_station.py +++ b/NuRadioReco/framework/base_station.py @@ -76,18 +76,18 @@ def remove_parameter(self, key): self._parameters.pop(key, None) def set_station_time(self, time, format=None): - """ + """ Set the (absolute) time for the station (stored as astropy.time.Time). Not related to the event._event_time. - + Parameters ---------- - + time: astropy.time.Time or datetime.datetime or float - If "time" is a float, you have to specify its format. - + If "time" is a float, you have to specify its format. + format: str - Only used when "time" is a float. Format to interpret "time". (Default: None) + Only used when "time" is a float. Format to interpret "time". (Default: None) """ if isinstance(time, datetime.datetime): @@ -100,26 +100,26 @@ def set_station_time(self, time, format=None): self._station_time = astropy.time.Time(time, format=format) def get_station_time(self, format='isot'): - """ - Returns a astropy.time.Time object - + """ + Returns a astropy.time.Time object + Parameters ---------- - + format: str Format in which the time object is displayed. (Default: isot) - + Returns ------- - + _station_time: astropy.time.Time """ if self._station_time is None: return None - + self._station_time.format = format return self._station_time - + def get_station_time_dict(self): """ Return the station time as dict {value, format}. Used for reading and writing """ if self._station_time is None: @@ -270,13 +270,13 @@ def serialize(self, save_efield_traces): trigger_pkls = [] for trigger in self._triggers.values(): trigger_pkls.append(trigger.serialize()) - + efield_pkls = [] for efield in self.get_electric_fields(): efield_pkls.append(efield.serialize(save_trace=save_efield_traces)) station_time_dict = self.get_station_time_dict() - + data = {'_parameters': NuRadioReco.framework.parameter_serialization.serialize(self._parameters), '_parameter_covariances': NuRadioReco.framework.parameter_serialization.serialize_covariances(self._parameter_covariances), '_ARIANNA_parameters': self._ARIANNA_parameters, @@ -286,7 +286,7 @@ def serialize(self, save_efield_traces): 'triggers': trigger_pkls, '_triggered': self._triggered, 'electric_fields': efield_pkls} - + return pickle.dumps(data, protocol=4) def deserialize(self, data_pkl): @@ -308,7 +308,7 @@ def deserialize(self, data_pkl): self._parameter_covariances = NuRadioReco.framework.parameter_serialization.deserialize_covariances( data['_parameter_covariances'], parameters.stationParameters) - + if '_ARIANNA_parameters' in data: self._ARIANNA_parameters = data['_ARIANNA_parameters'] diff --git a/NuRadioReco/framework/electric_field.py b/NuRadioReco/framework/electric_field.py index 3f1b60fe4..a207245ee 100644 --- a/NuRadioReco/framework/electric_field.py +++ b/NuRadioReco/framework/electric_field.py @@ -22,7 +22,7 @@ def __init__(self, channel_ids, position=None, Parameters ---------- channel_ids: array of ints - the channels ids this electric field is valid for. + the channels ids this electric field is valid for. (For cosmic rays one electric field is typically valid for several channels. For neutrino simulations, we typically simulate the electric field for each diff --git a/NuRadioReco/framework/event.py b/NuRadioReco/framework/event.py index 1d4c06128..49df21a37 100644 --- a/NuRadioReco/framework/event.py +++ b/NuRadioReco/framework/event.py @@ -41,7 +41,7 @@ def register_module_event(self, instance, name, kwargs): kwargs: the key word arguments of the run method """ - + self.__modules_event.append([name, instance, kwargs]) def register_module_station(self, station_id, instance, name, kwargs): @@ -61,7 +61,7 @@ def register_module_station(self, station_id, instance, name, kwargs): """ if station_id not in self.__modules_station: self.__modules_station[station_id] = [] - + iE = len(self.__modules_event) self.__modules_station[station_id].append([iE, name, instance, kwargs]) @@ -164,7 +164,7 @@ def get_station_ids(self): def set_station(self, station): self.__stations[station.get_id()] = station - + def has_triggered(self, trigger_name=None): """ Returns true if any station has been triggered. @@ -175,16 +175,16 @@ def has_triggered(self, trigger_name=None): * if None: The function returns False if not trigger was set. If one or multiple triggers were set, it returns True if any of those triggers triggered * if trigger name is set: return if the trigger with name 'trigger_name' has a trigger - + Returns ------- - + has_triggered : bool """ for station in self.get_stations(): if station.has_triggered(trigger_name): return True - + # if it reaches this point, no station has a trigger return False @@ -200,11 +200,11 @@ def add_particle(self, particle): if not isinstance(particle, NuRadioReco.framework.particle.Particle): logger.error("Requested to add non-Particle item to the list of particles. {particle} needs to be an instance of Particle.") raise TypeError("Requested to add non-Particle item to the list of particles. {particle} needs to be an instance of Particle.") - + if particle.get_id() in self.__particles: logger.error("MC particle with id {particle.get_id()} already exists. Simulated particle id needs to be unique per event") raise AttributeError("MC particle with id {particle.get_id()} already exists. Simulated particle id needs to be unique per event") - + self.__particles[particle.get_id()] = particle def get_particles(self): @@ -254,13 +254,13 @@ def has_particle(self, particle_id=None): """ if particle_id is None: return len(self.__particles) > 0 - + return particle_id in self.__particles.keys() def get_interaction_products(self, parent_particle, showers=True, particles=True): """ Return all the daughter particles and showers generated in the interaction of the - + Parameters ---------- showers: bool @@ -437,9 +437,9 @@ def serialize(self, mode): showers_pkl = [shower.serialize() for shower in self.get_showers()] sim_showers_pkl = [shower.serialize() for shower in self.get_sim_showers()] particles_pkl = [particle.serialize() for particle in self.get_particles()] - + hybrid_info = self.__hybrid_information.serialize() - + modules_out_event = [] for value in self.__modules_event: # remove module instances (this will just blow up the file size) modules_out_event.append([value[0], None, value[2]]) @@ -493,11 +493,11 @@ def deserialize(self, data_pkl): particle = NuRadioReco.framework.particle.Particle(None) particle.deserialize(particle_pkl) self.add_particle(particle) - + self.__hybrid_information = NuRadioReco.framework.hybrid_information.HybridInformation() if 'hybrid_info' in data.keys(): self.__hybrid_information.deserialize(data['hybrid_info']) - + self._parameters = data['_parameters'] self.__run_number = data['__run_number'] self._id = data['_id'] diff --git a/NuRadioReco/framework/parameters.py b/NuRadioReco/framework/parameters.py index b234ec112..eca29dd4b 100644 --- a/NuRadioReco/framework/parameters.py +++ b/NuRadioReco/framework/parameters.py @@ -1,7 +1,7 @@ """ Provides an interface to store simulated and reconstructed quantities -The parameters module provides access to store and read simulated or +The parameters module provides access to store and read simulated or reconstructed quantities in the different custom classes used in NuRadioMC. """ @@ -36,7 +36,7 @@ class stationParameters(Enum): cr_xmax = 28 #: Depth of shower maximum of the air shower vertex_2D_fit = 29 #: horizontal distance and z coordinate of the reconstructed vertex of the neutrino distance_correlations = 30 - shower_energy = 31 #: the energy of the shower + shower_energy = 31 #: the energy of the shower viewing_angles = 32 #: reconstructed viewing angles. A nested map structure. First key is channel id, second key is ray tracing solution id. Value is a float diff --git a/NuRadioReco/framework/particle.py b/NuRadioReco/framework/particle.py index da365b96b..6a55b9425 100644 --- a/NuRadioReco/framework/particle.py +++ b/NuRadioReco/framework/particle.py @@ -11,11 +11,11 @@ class Particle: def __init__(self, particle_index): - # "_id" is not the PDG code but a hierarchical index + # "_id" is not the PDG code but a hierarchical index # (PDG code is stored in _parameters["flavor"]) self._id = particle_index self._parameters = {} - + def __setitem__(self, key, value): self.set_parameter(key, value) @@ -25,7 +25,7 @@ def __getitem__(self, key): def get_id(self): """ Returns hierarchical index """ return self._id - + def __str__(self): msg = ( "Particle ({}): " @@ -35,7 +35,7 @@ def __str__(self): math.log10(self.get_parameter(parameters.particleParameters.energy)), math.cos(self.get_parameter(parameters.particleParameters.zenith))) ) - + return msg def get_parameter(self, key): @@ -55,7 +55,7 @@ def has_parameter(self, key): logger.error("parameter key needs to be of type NuRadioReco.framework.parameters.particleParameters") raise ValueError("parameter key needs to be of type NuRadioReco.framework.parameters.particleParameters") return key in self._parameters - + def as_hdf5_dict(self): hdf5_dict = collections.OrderedDict() hdf5_dict['azimuths'] = self.get_parameter(parameters.particleParameters.azimuth) diff --git a/NuRadioReco/modules/efieldRadioInterferometricReconstruction.py b/NuRadioReco/modules/efieldRadioInterferometricReconstruction.py index 99bf53517..f9b0f86cc 100644 --- a/NuRadioReco/modules/efieldRadioInterferometricReconstruction.py +++ b/NuRadioReco/modules/efieldRadioInterferometricReconstruction.py @@ -1,6 +1,6 @@ from NuRadioReco.modules.base.module import register_run from NuRadioReco.utilities import units, interferometry -from NuRadioReco.framework.parameters import stationParameters as stnp + from NuRadioReco.framework.parameters import showerParameters as shp from radiotools import helper as hp, coordinatesystems @@ -16,12 +16,12 @@ import logging logger = logging.getLogger('efieldRadioInterferometricReconstruction') -""" +""" This module hosts to classes - efieldInterferometricDepthReco - efieldInterferometricAxisReco (TODO: Not yet implemented) -The radio-interferometric reconstruction (RIT) was proposed in [1]. +The radio-interferometric reconstruction (RIT) was proposed in [1]. The implementation here is based on work published in [2]. [1]: H. Schoorlemmer, W. R. Carvalho Jr., arXiv:2006.10348 @@ -31,12 +31,12 @@ class efieldInterferometricDepthReco: """ - This class reconstructs the depth of the maximum of the longitudinal profile of the - beam-formed radio emission: X_RIT along a given axis. X_RIT is found to correlate with X_max. + This class reconstructs the depth of the maximum of the longitudinal profile of the + beam-formed radio emission: X_RIT along a given axis. X_RIT is found to correlate with X_max. This correlation may depend on the zenith angle, the frequency band, and detector layout (if the detector is very irregular). - For the reconstruction, a shower axis is needed. The radio emission in the vxB polarisation is used + For the reconstruction, a shower axis is needed. The radio emission in the vxB polarisation is used (and thus the arrival direction is needed). """ @@ -62,7 +62,7 @@ def begin(self, interpolation=True, signal_kind="power", debug=False): signal_kind : str Define which signal "metric" is used on the beamformed traces. Default "power" : sum over the squared amplitudes in a 100 ns window around the peak. - Other options are "amplitude" or "hilbert_sum" + Other options are "amplitude" or "hilbert_sum" debug : bool If true, show some debug plots (Default: False). @@ -79,7 +79,7 @@ def sample_longitudinal_profile( self, traces, times, station_positions, shower_axis, core, depths=None, distances=None): """ - Returns the longitudinal profile of the interferometic signal sampled along the shower axis. + Returns the longitudinal profile of the interferometic signal sampled along the shower axis. Parameters ---------- @@ -100,7 +100,7 @@ def sample_longitudinal_profile( Shower core. Keep in mind that the altitudes (z-coordinate) matters. depths : array (optinal) - Define the positions (slant depth along the axis) at which the interferometric signal is sampled. + Define the positions (slant depth along the axis) at which the interferometric signal is sampled. Instead of "depths" you can provide "distances". distances : array (optinal) @@ -109,7 +109,7 @@ def sample_longitudinal_profile( Returns ------- - + signals : array Interferometric singals sampled along the given axis """ @@ -165,9 +165,9 @@ def reconstruct_interferometric_depth( self, traces, times, station_positions, shower_axis, core, lower_depth=400, upper_depth=800, bin_size=100, return_profile=False): """ - Returns Gauss-parameters fitted to the "peak" of the interferometic + Returns Gauss-parameters fitted to the "peak" of the interferometic longitudinal profile along the shower axis. - + A initial samping range and size in defined by "lower_depth", "upper_depth", "bin_size". However if the "peak", i.e., maximum signal is found at an edge the sampling range in continually increased (with a min/max depth of 0/2000 g/cm^2). The Gauss is fitted around the @@ -201,26 +201,26 @@ def reconstruct_interferometric_depth( Define the step size pf the inital sampling (default: 100 g/cm2). The refined sampling around the peak region is / 10 this value. - return_profile : bool + return_profile : bool If true return the sampled profile in addition to the Gauss parameter (default: False). Returns ------- - + If return_profile is True - + depths_corse : np.array Depths along shower axis coarsely sampled - + depths_fine : np.array Depths along shower axis finely sampled (used in fitting) - + signals_corese : np.array Beamformed signals along shower axis coarsely sampled - + signals_fine : np.array Beamformed signals along shower axis finely sampled (used in fitting) - + popt : list List of fitted Gauss parameters (amplitude, position, width) @@ -228,7 +228,7 @@ def reconstruct_interferometric_depth( popt : list List of fitted Gauss parameters (amplitude, position, width) - + """ depths = np.arange(lower_depth, upper_depth, bin_size) @@ -280,7 +280,7 @@ def normal(x, A, x0, sigma): def update_atmospheric_model_and_refractivity_table(self, shower): - """ + """ Updates model of the atmosphere and tabulated, integrated refractive index according to shower properties. Parameters @@ -298,18 +298,18 @@ def update_atmospheric_model_and_refractivity_table(self, shower): self._at = models.Atmosphere(shower[shp.atmospheric_model]) self._tab = refractivity.RefractivityTable( self._at.model, refractivity_at_sea_level=shower[shp.refractive_index_at_ground] - 1, curved=True) - + elif self._tab._refractivity_at_sea_level != shower[shp.refractive_index_at_ground] - 1: self._tab = refractivity.RefractivityTable( self._at.model, refractivity_at_sea_level=shower[shp.refractive_index_at_ground] - 1, curved=True) - + else: pass @register_run() def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): - """ + """ Run interferometric reconstruction of depth of coherent signal. Parameters @@ -317,7 +317,7 @@ def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): evt : Event Event to run the module on. - + det : Detector Detector description @@ -328,7 +328,7 @@ def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): if true, take electric field trace from sim_station """ - # TODO: Mimic imperfect time syncronasation by adding a time jitter here? + # TODO: Mimic imperfect time syncronasation by adding a time jitter here? # TODO: Make it more flexible. Choose shower from which the geometry and atmospheric properties are taken. # Also store xrit in this shower. @@ -336,7 +336,7 @@ def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): shower = evt.get_first_sim_shower() else: shower = evt.get_first_shower() - + self.update_atmospheric_model_and_refractivity_table(shower) core, shower_axis, cs = get_geometry_and_transformation(shower) @@ -363,7 +363,7 @@ def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): shower.set_parameter(shp.interferometric_shower_maximum, xrit * units.g / units.cm2) #TODO: Add calibration Xmax(Xrit, theta, ...)? - + # for plotting self._data["xrit"].append(xrit) self._data["xmax"].append(shower[shp.shower_maximum] / (units.g / units.cm2)) @@ -388,7 +388,7 @@ def end(self): class efieldInterferometricAxisReco(efieldInterferometricDepthReco): """ - Class to reconstruct the shower axis with beamforming. + Class to reconstruct the shower axis with beamforming. """ def __init__(self): super().__init__() @@ -397,7 +397,7 @@ def __init__(self): def find_maximum_in_plane(self, xs, ys, p_axis, station_positions, traces, times, cs): """ Sample interferometric signals in 2-d plane (vxB-vxvxB) perpendicular to a given axis on a rectangular/quadratic grid. - The orientation of the plane is defined by the radiotools.coordinatesytem.cstrafo argument. + The orientation of the plane is defined by the radiotools.coordinatesytem.cstrafo argument. Parameters ---------- @@ -457,9 +457,9 @@ def sample_lateral_cross_section( relative=False, initial_grid_spacing=100, centered_around_truth=True, cross_section_size=1000, deg_resolution=np.deg2rad(0.005)): """ - Sampling the "cross section", i.e., 2d-lateral distribution of the beam formed signal for a slice in the atmosphere. - It is looking for the maximum in the lateral distribution with an (stupid) iterative grid search. - + Sampling the "cross section", i.e., 2d-lateral distribution of the beam formed signal for a slice in the atmosphere. + It is looking for the maximum in the lateral distribution with an (stupid) iterative grid search. + Returns the position and the strenght of the maximum signal. Parameters @@ -486,33 +486,33 @@ def sample_lateral_cross_section( cs : radiotools.coordinatesytem.cstrafo shower_axis_mc : np.array(3,) - + core_mc : np.array(3,) relative : bool False - + initial_grid_spacing : int 100 - + centered_around_truth : bool True cross_section_size : int 1000 - + deg_resolution : float np.deg2rad(0.005)) - + Returns ------- - + point_found : np.array(3,) Position of the found maximum weight : float Amplitude/Strengt of the maximum - + """ zenith_inital, _ = hp.cartesian_to_spherical( @@ -601,7 +601,7 @@ def sample_lateral_cross_section( def reconstruct_shower_axis( - self, + self, traces, times, station_positions, shower_axis, core, magnetic_field_vector, @@ -609,7 +609,7 @@ def reconstruct_shower_axis( initial_grid_spacing=60, cross_section_size=1000): """ - Run interferometric reconstruction of the shower axis. Find the maxima of the interferometric signals + Run interferometric reconstruction of the shower axis. Find the maxima of the interferometric signals within 2-d plane (slices) along a given axis (initial guess). Through those maxima (their position in the atmosphere) a straight line is fitted to reconstruct the shower axis. @@ -634,8 +634,8 @@ def reconstruct_shower_axis( is_mc : bool If true, interprete the provided shower axis as truth and add some gaussian smearing to optain an - inperfect initial guess for the shower axis (Default: True). - + inperfect initial guess for the shower axis (Default: True). + initial_grid_spacing : double Spacing of your grid points in meters (Default: 60m) @@ -644,7 +644,7 @@ def reconstruct_shower_axis( (Default: 1000m). """ - + if is_mc: zenith_mc, azimuth_mc = hp.cartesian_to_spherical(*shower_axis) @@ -656,8 +656,8 @@ def reconstruct_shower_axis( zenith=zenith_inital, azimuth=azimuth_inital) else: - shower_axis_inital = shower_axis - core_inital = core + shower_axis_inital = shower_axis + core_inital = core zenith_inital, azimuth_inital = hp.cartesian_to_spherical( *shower_axis) @@ -667,7 +667,7 @@ def reconstruct_shower_axis( cs = coordinatesystems.cstrafo( zenith_inital, azimuth_inital, magnetic_field_vector=magnetic_field_vector) - + if is_mc: core_inital = cs.transform_from_vxB_vxvxB_2D( np.array([np.random.normal(0, 100), np.random.normal(0, 100), 0]), core) @@ -682,15 +682,15 @@ def reconstruct_shower_axis( centered_around_truth = True def sample_lateral_cross_section_placeholder(dep): - """ + """ Run sample_lateral_cross_section for a particular depth. - + Parameters ---------- dep : double Depth along the axis at which the cross section is sampled in g/cm2. - + """ return self.sample_lateral_cross_section( traces, times, station_positions, @@ -745,7 +745,7 @@ def sample_lateral_cross_section_placeholder(dep): @register_run() def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): - """ + """ Run interferometric reconstruction of depth of coherent signal. Parameters @@ -753,7 +753,7 @@ def run(self, evt, det, use_MC_geometry=True, use_MC_pulses=True): evt : Event Event to run the module on. - + det : Detector Detector description @@ -790,7 +790,7 @@ def end(self): def get_geometry_and_transformation(shower): - """ + """ Returns core (def. as intersection between shower axis and observation plane, shower axis, and radiotools.coordinatesytem for given shower. @@ -819,9 +819,9 @@ def get_geometry_and_transformation(shower): def get_station_data(evt, det, cs, use_MC_pulses, n_sampling=None): - """ + """ Returns station data in a proper format - + Parameters ---------- @@ -839,15 +839,15 @@ def get_station_data(evt, det, cs, use_MC_pulses, n_sampling=None): Returns ------- - + traces_vxB : np.array The electric field traces in the vxB polarisation (takes first electric field stored in a station) for all stations/observers. - times : mp.array + times : mp.array The electric field traces time series for all stations/observers. - + pos : np.array - Positions for all stations/observers. + Positions for all stations/observers. """ traces_vxB = [] @@ -869,11 +869,11 @@ def get_station_data(evt, det, cs, use_MC_pulses, n_sampling=None): hw = n_sampling // 2 m = np.argmax(np.abs(trace_vxB)) - if m < hw: + if m < hw: m = hw if m > len(trace_vxB) - hw: m = len(trace_vxB) - hw - + trace_vxB = trace_vxB[m-hw:m+hw] time = time[m-hw:m+hw] @@ -882,7 +882,7 @@ def get_station_data(evt, det, cs, use_MC_pulses, n_sampling=None): break # just take the first efield. TODO: Improve this pos.append(det.get_absolute_position(station.get_id())) - + traces_vxB = np.array(traces_vxB) times = np.array(times) pos = np.array(pos) @@ -891,7 +891,7 @@ def get_station_data(evt, det, cs, use_MC_pulses, n_sampling=None): def plot_lateral_cross_section(xs, ys, signals, mc_pos=None, fname=None, title=None): - """ + """ Plot the lateral distribution of the beamformed singal (in the vxB, vxvxB directions). Parameters @@ -901,7 +901,7 @@ def plot_lateral_cross_section(xs, ys, signals, mc_pos=None, fname=None, title=N Positions on x-axis (vxB) at which the signal is sampled (on a 2d grid) ys : np.array - Positions on y-axis (vxvxB) at which the signal is sampled (on a 2d grid) + Positions on y-axis (vxvxB) at which the signal is sampled (on a 2d grid) signals : np.array Signals sampled on the 2d grid defined by xs and ys. diff --git a/NuRadioReco/utilities/interferometry.py b/NuRadioReco/utilities/interferometry.py index 2d7505d15..a29b0a54f 100644 --- a/NuRadioReco/utilities/interferometry.py +++ b/NuRadioReco/utilities/interferometry.py @@ -10,9 +10,9 @@ def get_signal(sum_trace, tstep, window_width=100 * units.ns, kind="power"): - """ - Calculates signal quantity from beam-formed waveform - + """ + Calculates signal quantity from beam-formed waveform + Parameters ---------- @@ -21,13 +21,13 @@ def get_signal(sum_trace, tstep, window_width=100 * units.ns, kind="power"): tstep : double Sampling bin size - + window_width : double - Time window size to calculate power + Time window size to calculate power kind : str Key-word what to do: "amplitude", "power", or "hilbert_sum" - + Returns ------- @@ -70,11 +70,11 @@ def get_signal(sum_trace, tstep, window_width=100 * units.ns, kind="power"): def interfere_traces_interpolation(target_pos, positions, traces, times, tab): - """ + """ Calculate sum of time shifted waveforms. Performs a linear interpolation between samples. - + Parameters ---------- @@ -92,7 +92,7 @@ def interfere_traces_interpolation(target_pos, positions, traces, times, tab): tab : radiotools.atmosphere.refractivity.RefractivityTable Tabulated table of the avg. refractive index between two points - + Returns ------- @@ -133,9 +133,9 @@ def interfere_traces_interpolation(target_pos, positions, traces, times, tab): def get_time_shifts(target_pos, positions, tab): """ - Calculates the time delay of an electromagnetic wave along a straight trajectories between + Calculates the time delay of an electromagnetic wave along a straight trajectories between a source/traget location and several observers. - + Parameters ---------- @@ -153,7 +153,7 @@ def get_time_shifts(target_pos, positions, tab): tshifts : np.array(n,) Time delay in sec - + """ tshifts = np.zeros(len(positions)) @@ -169,13 +169,13 @@ def get_time_shifts(target_pos, positions, tab): def fit_axis(z, theta, phi, coreX, coreY): - """ + """ Predicts the intersetction of an axis/line with horizontal layers at different heights. Line is described by an anchor on a horizontal plane (coreX, coreY) and a direction in spherical coordinates (theta, phi). - Returns the position/intersection of the line with flat horizontal layers at + Returns the position/intersection of the line with flat horizontal layers at given height(s) z. Resulting array (positions) is flatten. Parameters @@ -231,7 +231,7 @@ def get_intersection_between_line_and_plane(plane_normal, plane_anchor, line_dir Anchor of this line epsilon : double - Numerical precision + Numerical precision Returns ------- From 3402b10556c43d62b5af16f66aa1ca990520a82a Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 22 Dec 2023 08:16:07 +0000 Subject: [PATCH 41/54] improve logging --- NuRadioMC/simulation/simulation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 469f6e120..21a7d48da 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -380,7 +380,8 @@ def __init__(self, inputfilename, logger.status(f'Station.channel {station_id}.{channel_id:02d}: noise temperature = {noise_temp_channel} K, ' f'est. bandwidth = {integrated_channel_response / mean_integrated_response / units.MHz:.2f} MHz, ' f'max. filter amplification = {max_amplification:.2e} -> Vrms = ' - f'{self._Vrms_per_channel[station_id][channel_id] / units.mV:.2f} mV -> efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (VEL = 1m) ') + f'integrated response = {integrated_channel_response / units.MHz:.2e}MHz -> Vrms = ' + f'{self._Vrms_per_channel[station_id][channel_id] / units.mV:.2f} mV -> efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (assuming VEL = 1m) ') self._Vrms = next(iter(next(iter(self._Vrms_per_channel.values())).values())) @@ -403,7 +404,8 @@ def __init__(self, inputfilename, logger.status(f'Station.channel {station_id}.{channel_id:02d}: ' f'est. bandwidth = {integrated_channel_response / mean_integrated_response / units.MHz:.2f} MHz, ' f'max. filter amplification = {max_amplification:.2e} -> ' - f'efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (VEL = 1m) ') + f'integrated response = {integrated_channel_response / units.MHz:.2e}MHz -> Vrms = ' + f'efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (assuming VEL = 1m) ') else: raise AttributeError(f"noise temperature and Vrms are both set to None") From e6a3330143c8adacd34b8d964479b5fe483f6592 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 22 Dec 2023 09:07:22 +0000 Subject: [PATCH 42/54] fix logging outptut --- NuRadioMC/simulation/simulation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 21a7d48da..bdca470c3 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -403,8 +403,8 @@ def __init__(self, inputfilename, logger.status(f'Station.channel {station_id}.{channel_id:02d}: ' f'est. bandwidth = {integrated_channel_response / mean_integrated_response / units.MHz:.2f} MHz, ' - f'max. filter amplification = {max_amplification:.2e} -> ' - f'integrated response = {integrated_channel_response / units.MHz:.2e}MHz -> Vrms = ' + f'max. filter amplification = {max_amplification:.2e} ' + f'integrated response = {integrated_channel_response / units.MHz:.2e}MHz ->' f'efield Vrms = {self._Vrms_efield_per_channel[station_id][channel_id] / units.V / units.m / units.micro:.2f}muV/m (assuming VEL = 1m) ') else: From a87f413411f0b35a42b00b746d19d434bb4b9787 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Fri, 22 Dec 2023 10:32:53 +0100 Subject: [PATCH 43/54] update version number for release 2.2.0 --- changelog.txt | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 578b65d31..92f29fa23 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,7 @@ Changelog - to keep track of all relevant changes please update the categories "new features" and "bugfixes" before a pull request merge! -version 2.2.0-dev +version 2.2.0 new features: - expand values stored in SimplePhasedTrigger - added getting to query ARZ charge-excess profiles diff --git a/pyproject.toml b/pyproject.toml index b525276d5..b70d3feca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "NuRadioMC" -version = "2.2.0-dev" +version = "2.2.0" authors = ["Christian Glaser et al."] homepage = "https://github.com/nu-radio/NuRadioMC" documentation = "https://nu-radio.github.io/NuRadioMC/main.html" From 133e6b45b2bf0fdee97afc2ed60a3b32d00b5315 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 22 Dec 2023 10:03:40 +0000 Subject: [PATCH 44/54] correctly initialize dictionary --- NuRadioMC/simulation/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index bdca470c3..70924fb58 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -354,7 +354,7 @@ def __init__(self, inputfilename, self._noiseless_channels = collections.defaultdict(dict) for station_id in self._integrated_channel_response: - + self._noiseless_channels[station_id] = [] for channel_id in self._integrated_channel_response[station_id]: if self._noise_temp is None: noise_temp_channel = self._det.get_noise_temperature(station_id, channel_id) From 2adda9733e3ae0a40fbd15f6a3c225f91e8b91ae Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 22 Dec 2023 10:17:34 +0000 Subject: [PATCH 45/54] update version number to new development version --- changelog.txt | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 92f29fa23..37736aac0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,12 @@ Changelog - to keep track of all relevant changes please update the categories "new features" and "bugfixes" before a pull request merge! +version 2.3.0-dev +new features: + +bugfixes: + + version 2.2.0 new features: - expand values stored in SimplePhasedTrigger diff --git a/pyproject.toml b/pyproject.toml index b70d3feca..a054b8c3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "NuRadioMC" -version = "2.2.0" +version = "2.3.0-dev" authors = ["Christian Glaser et al."] homepage = "https://github.com/nu-radio/NuRadioMC" documentation = "https://nu-radio.github.io/NuRadioMC/main.html" From 486c168e0381a99508be10a769c1aed797319ad6 Mon Sep 17 00:00:00 2001 From: Christian Glaser Date: Fri, 22 Dec 2023 11:26:12 +0000 Subject: [PATCH 46/54] update reference list --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 385697f19..e5c0966f8 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,10 @@ Also please visit https://nu-radio.github.io/NuRadioMC/Introduction/pages/contri ## Publications builing up on NuRadioMC/Reco NuRadioMC is used in an increasing number of studies. To get an overview for what NuRadioMC can be used for, please have a look at the following publications or see [here](https://inspirehep.net/literature?sort=mostrecent&size=25&page=1&q=refersto%3Arecid%3A1738571%20or%20refersto%3Arecid%3A1725583): +* V. Valera, M. Bustamante, O. Mena, "Joint measurement of the ultra-high-energy neutrino spectrum and cross section", [arXiv:2308.07709](https://arxiv.org/abs/2308.07709) * IceCube-Gen2 collaboration, [IceCube-Gen2 Technical Design Report](https://icecube-gen2.wisc.edu/science/publications/TDR) * ARIANNA collaboration (A. Anker et al.), "Developing New Analysis Tools for Near Surface Radio-based Neutrino Detectors", [arXiv:2307.07188](https://arxiv.org/abs/2307.07188) -* L. Pyras, C. Glaser S. Hallmann and A. Nelles, "Atmospheric muons at PeV energies in radio neutrino detectors", [arXiv:2307.04736](https://arxiv.org/abs/2307.04736) +* L. Pyras, C. Glaser S. Hallmann and A. Nelles, "Atmospheric muons at PeV energies in radio neutrino detectors", JCAP 10 (2023) 043, [arXiv:2307.04736](https://arxiv.org/abs/2307.04736) * I. Plaisier, S. Bouma, A. Nelles, "Reconstructing the arrival direction of neutrinos in deep in-ice radio detectors", [arXiv:2302.00054](https://arxiv.org/abs/2302.00054) * S. Bouma, A. Nelles for the IceCube-Gen2 collaboration, "Direction reconstruction performance for IceCube-Gen2 Radio", [PoS(ICRC2023)1045](https://pos.sissa.it/444/1045/pdf) * F. Schlüter and S. Toscano for the IceCube-Gen2 collaboration, "Estimating the coincidence rate between the optical and radio array of IceCube-Gen2", [PoS(ICRC2023)1022](https://pos.sissa.it/444/1022/pdf) From 779573db12d0e161cbbfec428ead96cc69d4e338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= <30903175+fschlueter@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:01:13 +0100 Subject: [PATCH 47/54] fix: use defaultdict with correct type --- NuRadioMC/simulation/simulation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NuRadioMC/simulation/simulation.py b/NuRadioMC/simulation/simulation.py index 70924fb58..d31f5a0c8 100644 --- a/NuRadioMC/simulation/simulation.py +++ b/NuRadioMC/simulation/simulation.py @@ -352,9 +352,8 @@ def __init__(self, inputfilename, self._noise_temp = float(noise_temp) logger.status(f"Use a noise temperature of {noise_temp / units.kelvin:.1f} K for each channel to determine noise Vrms.") - self._noiseless_channels = collections.defaultdict(dict) + self._noiseless_channels = collections.defaultdict(list) for station_id in self._integrated_channel_response: - self._noiseless_channels[station_id] = [] for channel_id in self._integrated_channel_response[station_id]: if self._noise_temp is None: noise_temp_channel = self._det.get_noise_temperature(station_id, channel_id) From b45831ff7f24776a6dff1407ee4c16f84c63b723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schl=C3=BCter?= Date: Thu, 4 Jan 2024 10:17:47 +0100 Subject: [PATCH 48/54] Implements comments --- NuRadioMC/EvtGen/generator.py | 60 +++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/NuRadioMC/EvtGen/generator.py b/NuRadioMC/EvtGen/generator.py index 21fc114cc..add827d12 100644 --- a/NuRadioMC/EvtGen/generator.py +++ b/NuRadioMC/EvtGen/generator.py @@ -427,7 +427,6 @@ def set_volume_attributes(volume, proposal, attributes): Parameters ---------- - volume: dict A dictionary specifying the simulation volume. Can be either a cylinder (specified with min/max radius and z-coordinate) or a cube (specified with min/max x-, y-, z-coordinates). @@ -436,7 +435,6 @@ def set_volume_attributes(volume, proposal, attributes): * fiducial_{rmin,rmax,zmin,zmax}: float lower r / upper r / lower z / upper z coordinate of fiducial volume - * full_{rmin,rmax,zmin,zmax}: float (optional) lower r / upper r / lower z / upper z coordinate of simulated volume @@ -444,7 +442,6 @@ def set_volume_attributes(volume, proposal, attributes): * fiducial_{xmin,xmax,ymin,ymax,zmin,zmax}: float lower / upper x-, y-, z-coordinate of fiducial volume - * full_{xmin,xmax,ymin,ymax,zmin,zmax}: float (optional) lower / upper x-, y-, z-coordinate of simulated volume @@ -452,10 +449,8 @@ def set_volume_attributes(volume, proposal, attributes): * x0, y0: float x-, y-coordinate this shift the center of the simulation volume - proposal: bool specifies if secondary interaction via proposal are calculated - attributes: dictionary dict storing hdf5 attributes """ @@ -530,7 +525,7 @@ def set_volume_attributes(volume, proposal, attributes): logger.info(f"decreasing zmin from {attributes['fiducial_zmin'] / units.km:.01f}km to {zmin / units.km:.01f}km") n_events = int(n_events * volume_full / volume_fiducial) - logger.info(f"increasing number of events to from {attributes['n_events']:.6g} {n_events:.6g}") + logger.info(f"increasing number of events from {attributes['n_events']:.6g} to {n_events:.6g}") attributes['n_events'] = n_events attributes['rmin'] = rmin @@ -543,13 +538,6 @@ def set_volume_attributes(volume, proposal, attributes): attributes['area'] = np.pi * (rmax ** 2 - rmin ** 2) elif "fiducial_xmax" in volume: # user specifies a cube - attributes['fiducial_xmax'] = volume['fiducial_xmax'] - attributes['fiducial_xmin'] = volume['fiducial_xmin'] - attributes['fiducial_ymax'] = volume['fiducial_ymax'] - attributes['fiducial_ymin'] = volume['fiducial_ymin'] - attributes['fiducial_zmin'] = volume['fiducial_zmin'] - attributes['fiducial_zmax'] = volume['fiducial_zmax'] - # copy keys for key in ['fiducial_xmin', 'fiducial_xmax', 'fiducial_ymin', 'fiducial_ymax', 'fiducial_zmin', 'fiducial_zmax']: attributes[key] = volume[key] @@ -621,7 +609,7 @@ def set_volume_attributes(volume, proposal, attributes): attributes['volume'] = V # save full simulation volume to simplify effective volume calculation attributes['area'] = (xmax - xmin) * (ymax - ymin) else: - raise AttributeError(f"'fiducial_xmax' or 'fiducial_rmax' is not part of 'volume'. Can not define a volume") + raise AttributeError("'fiducial_xmax' or 'fiducial_rmax' is not part of 'volume'. Can not define a volume") def generate_vertex_positions(attributes, n_events, rnd=None): @@ -799,7 +787,27 @@ def generate_surface_muons(filename, n_events, Emin, Emax, the maximum neutrino energy (energies are randomly chosen assuming a uniform distribution in the logarithm of the energy) volume: dict - a dictionary specifying the simulation volume. See set_volume_attributes() for an explanation + A dictionary specifying the simulation volume. Can be either a cylinder (specified with min/max radius and z-coordinate) + or a cube (specified with min/max x-, y-, z-coordinates). For more information see set_volume_attributes + + Cylinder: + + * fiducial_{rmin,rmax,zmin,zmax}: float + lower r / upper r / lower z / upper z coordinate of fiducial volume + * full_{rmin,rmax,zmin,zmax}: float (optional) + lower r / upper r / lower z / upper z coordinate of simulated volume + + Cube: + + * fiducial_{xmin,xmax,ymin,ymax,zmin,zmax}: float + lower / upper x-, y-, z-coordinate of fiducial volume + * full_{xmin,xmax,ymin,ymax,zmin,zmax}: float (optional) + lower / upper x-, y-, z-coordinate of simulated volume + + Optinal you can also define the horizontal center of the volume (if you want to displace it from the origin) + + * x0, y0: float + x-, y-coordinate this shift the center of the simulation volume thetamin: float lower zenith angle for neutrino arrival direction thetamax: float @@ -1066,7 +1074,27 @@ def generate_eventlist_cylinder(filename, n_events, Emin, Emax, Emax: float the maximum neutrino energy volume: dict - a dictionary specifying the simulation volume. See set_volume_attributes() for an explanation + A dictionary specifying the simulation volume. Can be either a cylinder (specified with min/max radius and z-coordinate) + or a cube (specified with min/max x-, y-, z-coordinates). For more information see set_volume_attributes + + Cylinder: + + * fiducial_{rmin,rmax,zmin,zmax}: float + lower r / upper r / lower z / upper z coordinate of fiducial volume + * full_{rmin,rmax,zmin,zmax}: float (optional) + lower r / upper r / lower z / upper z coordinate of simulated volume + + Cube: + + * fiducial_{xmin,xmax,ymin,ymax,zmin,zmax}: float + lower / upper x-, y-, z-coordinate of fiducial volume + * full_{xmin,xmax,ymin,ymax,zmin,zmax}: float (optional) + lower / upper x-, y-, z-coordinate of simulated volume + + Optinal you can also define the horizontal center of the volume (if you want to displace it from the origin) + + * x0, y0: float + x-, y-coordinate this shift the center of the simulation volume thetamin: float lower zenith angle for neutrino arrival direction (default 0deg) thetamax: float From 41682042416356685ad0c8b762024302ee2cf0e2 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 9 Jan 2024 11:33:58 +0100 Subject: [PATCH 49/54] change relative path to abspath in os.symlink --- NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py index 4d1355026..fbe8b6753 100644 --- a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py +++ b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py @@ -393,7 +393,7 @@ def begin(self, self.__temporary_dirs.append(path) # for housekeeping self.logger.debug(f"Create symlink for {dir_file}") - os.symlink(dir_file, os.path.join(path, "combined.root")) + os.symlink(os.path.abspath(dir_file), os.path.join(path, "combined.root")) dir_file = path # set path to e.g. /tmp/NuRadioReco_XXXXXXX/combined.root From 679cb88e9abe24ec9b324238946744d000bb87ab Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 9 Jan 2024 12:04:07 +0100 Subject: [PATCH 50/54] improve some docstrings --- .../modules/io/RNO_G/readRNOGDataMattak.py | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py index fbe8b6753..90834e2cb 100644 --- a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py +++ b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py @@ -186,11 +186,17 @@ class readRNOGData: def __init__(self, run_table_path=None, load_run_table=True, log_level=logging.INFO): """ + Reader for RNO-G ``.root`` files + + This class provides read access to RNO-G ``.root`` files and converts them + to NuRadioMC :class:`Events `. Requires ``mattak`` + (https://github.com/RNO-G/mattak) to be installed. + Parameters ---------- run_table_path: str | None - Path to a run_table.cvs file. If None, the run table is queried from the DB. (Default: None) + Path to a run_table.csv file. If None, the run table is queried from the DB. (Default: None) load_run_table: bool If True, try to load the run_table from run_table_path. Otherwise, skip this. @@ -198,6 +204,23 @@ def __init__(self, run_table_path=None, load_run_table=True, log_level=logging.I log_level: enum Set verbosity level of logger. If logging.DEBUG, set mattak to verbose (unless specified in mattak_kwargs). (Default: logging.INFO) + + Examples + -------- + + .. code-block:: + + reader = readRNOGDataMattak.readRNOGData() # initialize reader + reader.begin('/path/to/root_file_or_folder') + + evt = reader.get_event_by_index(0) # returns the first event in the file + # OR + evt = reader.get_event(run_nr=1100, event_id=679) # returns the event with run_number 1100 and event_id 679 + # OR + for evt in reader.run(): # loop over all events in file + # perform some analysis + pass + """ self.logger = logging.getLogger('NuRadioReco.readRNOGData') self.logger.setLevel(log_level) @@ -721,10 +744,10 @@ def run(self): """ Loop over all events. - Returns - ------- + Yields + ------ - evt: generator(NuRadioReco.framework.event) + evt: `NuRadioReco.framework.event.Event` """ event_idx = -1 for dataset in self._datasets: @@ -774,7 +797,7 @@ def get_event_by_index(self, event_index): Returns ------- - evt: NuRadioReco.framework.event + evt: `NuRadioReco.framework.event.Event` """ self.logger.debug(f"Processing event number {event_index} out of total {self._n_events_total}") @@ -816,7 +839,7 @@ def get_event(self, run_nr, event_id): Returns ------- - evt: NuRadioReco.framework.event + evt: `NuRadioReco.framework.event.Event` """ self.logger.debug(f"Processing event {event_id}") From fbc9691a31a88b6889def12a23cdb07d6f074413 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 9 Jan 2024 12:10:16 +0100 Subject: [PATCH 51/54] make some readRNOGDataMattak functions protected to reduce clutter --- .../modules/io/RNO_G/readRNOGDataMattak.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py index 90834e2cb..0056f5238 100644 --- a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py +++ b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py @@ -22,7 +22,7 @@ import random -def create_random_directory_path(prefix="/tmp/", n=7): +def _create_random_directory_path(prefix="/tmp/", n=7): """ Produces a path for a temporary directory with a n letter random suffix @@ -48,7 +48,7 @@ def create_random_directory_path(prefix="/tmp/", n=7): return path -def baseline_correction(wfs, n_bins=128, func=np.median, return_offsets=False): +def _baseline_correction(wfs, n_bins=128, func=np.median, return_offsets=False): """ Simple baseline correction function. @@ -150,7 +150,7 @@ def get_time_offset(trigger_type): raise KeyError(f"Unknown trigger type: {trigger_type}. Known are: {known_trigger_types}. Abort ....") -def all_files_in_directory(mattak_dir): +def _all_files_in_directory(mattak_dir): """ Checks if all Mattak root files are in a directory. Ignoring runinfo.root because (asaik) not all runs have those and information is currently not read by Mattak. @@ -376,14 +376,14 @@ def begin(self, self._datasets = [] self.__n_events_per_dataset = [] + if not isinstance(dirs_files, (list, np.ndarray)): + dirs_files = [dirs_files] + self.logger.info(f"Parse through / read-in {len(dirs_files)} directory(ies) / file(s).") self.__skipped_runs = 0 self.__n_runs = 0 - if not isinstance(dirs_files, (list, np.ndarray)): - dirs_files = [dirs_files] - # Set verbose for mattak if "verbose" in mattak_kwargs: verbose = mattak_kwargs.pop("verbose") @@ -398,7 +398,7 @@ def begin(self, if os.path.isdir(dir_file): - if not all_files_in_directory(dir_file): + if not _all_files_in_directory(dir_file): self.logger.error(f"Incomplete directory: {dir_file}. Skip ...") continue else: @@ -407,7 +407,7 @@ def begin(self, # it is not a combined file). To work around this: Create a tmp directory under `/tmp/`, link the file you want to # read into this directory with the the name `combined.root`, use this path to read the run. - path = create_random_directory_path() + path = _create_random_directory_path() self.logger.debug(f"Create temporary directory: {path}") if os.path.exists(path): raise ValueError(f"Temporary directory {path} already exists.") From 7146153fcfb49b4279d5cc5678100ff41f2126bb Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Tue, 9 Jan 2024 12:24:47 +0100 Subject: [PATCH 52/54] add warning if user tries to pass .root files to readRNOGData.__init__ --- NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py index 0056f5238..fa865277e 100644 --- a/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py +++ b/NuRadioReco/modules/io/RNO_G/readRNOGDataMattak.py @@ -245,6 +245,18 @@ def __init__(self, run_table_path=None, load_run_table=True, log_level=logging.I self.logger.warn("Import of run table failed. Runs can not be filtered.! \n" "You can get the interface from GitHub: git@github.com:RNO-G/rnog-runtable.git") else: + # some users may mistakenly try to pass the .root files to __init__ + # we check for this and raise a (hopefully) helpful error message + user_passed_root_file_msg = ( + "The optional argument run_table_path expects a csv file, " + "but you passed a list of files or a .root file. Note that " + "the .root files to read in should be passed to the `begin` method of this class" + ) + if isinstance(run_table_path, (list, np.ndarray)): + raise TypeError(user_passed_root_file_msg) + elif os.path.isdir(run_table_path) or run_table_path.endswith('.root'): + raise ValueError(user_passed_root_file_msg) + import pandas self.__run_table = pandas.read_csv(run_table_path) From 438e64973341275f96b6d3a74a194a2f9ee75939 Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Thu, 11 Jan 2024 10:16:26 +0100 Subject: [PATCH 53/54] update version number and changelog --- changelog.txt | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 92f29fa23..855407f04 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,10 @@ Changelog - to keep track of all relevant changes please update the categories "new features" and "bugfixes" before a pull request merge! +version 2.2.1 +bugfixes: +- readRNOGDataMattak: fix bug where .root files would not be found if they are passed as relative paths (instead of absolute paths or folder) + version 2.2.0 new features: - expand values stored in SimplePhasedTrigger diff --git a/pyproject.toml b/pyproject.toml index b70d3feca..29091a6fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "NuRadioMC" -version = "2.2.0" +version = "2.2.1" authors = ["Christian Glaser et al."] homepage = "https://github.com/nu-radio/NuRadioMC" documentation = "https://nu-radio.github.io/NuRadioMC/main.html" From dc8796cb0bf08ee584041b1fdbcc03411fd2246e Mon Sep 17 00:00:00 2001 From: Sjoerd Bouma Date: Mon, 22 Jan 2024 21:29:38 +0100 Subject: [PATCH 54/54] [RNO-G] include zero-meaning also in approximate block offset removal --- NuRadioReco/modules/RNO_G/channelBlockOffsetFitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuRadioReco/modules/RNO_G/channelBlockOffsetFitter.py b/NuRadioReco/modules/RNO_G/channelBlockOffsetFitter.py index e4711e094..d306bba24 100644 --- a/NuRadioReco/modules/RNO_G/channelBlockOffsetFitter.py +++ b/NuRadioReco/modules/RNO_G/channelBlockOffsetFitter.py @@ -217,7 +217,7 @@ def fit_block_offsets( # obtain guesses for block offsets a_guess = np.mean(np.split(filtered_trace, n_blocks), axis=1) if mode == 'approximate': - block_offsets = a_guess + block_offsets = a_guess + np.mean(trace) elif mode == 'fit': # self._offset_guess[channel_id] = a_guess # we can get rid of one parameter through a global shift