def generate_noise(self): """ generates noise traces for all channels that will cause a high/low majority logic trigger Returns np.array of shape (n_channels, n_samples) """ n_traces = [None] * self.n_majority t_bins = [None] * self.n_majority for iCh in range(self.n_majority): while n_traces[iCh] is None: spec = self.noise.bandlimited_noise(self.min_freq, self.max_freq, self.n_samples, self.sampling_rate, self.amplitude, self.noise_type, time_domain=False) spec *= self.filt trace = fft.freq2time(spec, self.sampling_rate) if (np.any(trace > self.threshold) and np.any(trace < -self.threshold)): triggered_bins = get_high_low_triggers( trace, self.threshold, -self.threshold, self.time_coincidence, self.dt) if (True in triggered_bins): t_bins[iCh] = triggered_bins if (iCh == 0): n_traces[iCh] = np.roll( trace, self.trigger_bin - np.argwhere(triggered_bins is True)[0]) else: tmp = np.random.randint(self.trigger_bin_low, self.trigger_bin) n_traces[iCh] = np.roll( trace, tmp - np.argwhere(triggered_bins is True)[0]) traces = np.zeros((self.n_channels, self.n_samples)) rnd_iterator = list(range(self.n_channels)) np.random.shuffle(rnd_iterator) for i, iCh in enumerate(rnd_iterator): if (i < self.n_majority): traces[iCh] = n_traces[i] else: spec = self.noise.bandlimited_noise(self.min_freq, self.max_freq, self.n_samples, self.sampling_rate, self.amplitude, type=self.noise_type, time_domain=False) spec *= self.filt traces[iCh] = fft.freq2time(spec, self.sampling_rate) return traces
def butterworth_filter_trace(trace, sampling_frequency, passband, order=8): """ Filters a trace using a Butterworth filter. Parameters ---------- trace: array of floats Trace to be filtered sampling_frequency: float Sampling frequency passband: (float, float) tuple Tuple indicating the cutoff frequencies order: integer Filter order Returns ------ filtered_trace: array of floats The filtered trace """ n_samples = len(trace) spectrum = fft.time2freq(trace, sampling_frequency) frequencies = np.fft.rfftfreq(n_samples, 1 / sampling_frequency) filtered_spectrum = apply_butterworth(spectrum, frequencies, passband, order) filtered_trace = fft.freq2time(filtered_spectrum, sampling_frequency) return filtered_trace
def get_channel_voltage_from_efield(station, electric_field, channels, detector, zenith, azimuth, antenna_pattern_provider, return_spectrum=True): """ Returns the voltage traces that would result in the channels from the station's E-field. Parameters ------------------------ station: Station electric_field: ElectricField channels: array of int IDs of the channels for which the expected voltages should be calculated detector: Detector zenith, azimuth: float incoming direction of the signal. Note that reflection and refraction at the air/ice boundary are already being taken into account. antenna_pattern_provider: AntennaPatternProvider return_spectrum: boolean if True, returns the spectrum, if False return the time trace """ frequencies = electric_field.get_frequencies() spectrum = electric_field.get_frequency_spectrum() efield_antenna_factor = get_efield_antenna_factor(station, frequencies, channels, detector, zenith, azimuth, antenna_pattern_provider) if return_spectrum: voltage_spectrum = np.zeros((len(channels), len(frequencies)), dtype=np.complex) for i_ch, ch in enumerate(channels): voltage_spectrum[i_ch] = np.sum(efield_antenna_factor[i_ch] * np.array([spectrum[1], spectrum[2]]), axis=0) return voltage_spectrum else: voltage_trace = np.zeros((len(channels), 2 * (len(frequencies) - 1)), dtype=np.complex) for i_ch, ch in enumerate(channels): voltage_trace[i_ch] = fft.freq2time(np.sum(efield_antenna_factor[i_ch] * np.array([spectrum[1], spectrum[2]]), axis=0), electric_field.get_sampling_rate()) return np.real(voltage_trace)
def obj_xcorr(params): if(len(params) == 3): slope, ratio2, phase2 = params ratio = (np.arctan(ratio2) + np.pi * 0.5) / np.pi # project -inf..inf on 0..1 elif(len(params) == 2): slope, ratio2 = params phase2 = 0 ratio = (np.arctan(ratio2) + np.pi * 0.5) / np.pi # project -inf..inf on 0..1 elif(len(params) == 1): phase2 = 0 ratio = 0 slope = params[0] phase = np.arctan(phase2) # project -inf..+inf to -0.5 pi..0.5 pi analytic_pulse_theta = pulse.get_analytic_pulse_freq(ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse_freq(1 - ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) chi2 = 0 n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) positions = np.zeros(n_channels, dtype=np.int) max_xcorrs = np.zeros(n_channels) # first determine the position with the larges xcorr for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) xcorr = np.abs(hp.get_normalized_xcorr(trace, analytic_traces[iCh])) positions[iCh] = np.argmax(np.abs(xcorr)) + 1 max_xcorrs[iCh] = xcorr.max() chi2 -= xcorr.max() logger.debug("ratio = {:.2f}, slope = {:.4g}, phase = {:.0f} ({:.4f}), chi2 = {:.4g}".format(ratio, slope, phase / units.deg, phase2, chi2)) return chi2
def obj_amplitude_second_order(params, slope, phase, pos, compare='hilbert', debug_obj=0): ampPhi, ampTheta, second_order = params analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0]) analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0]) chi2 = 0 if(debug_obj and self.i_slope_fit_iterations % 50 == 0): fig, ax = plt.subplots(5, 2, sharex=False, figsize=(20, 10)) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) # first determine the position with the larges xcorr channel_max = 0 for trace in V_timedomain: if np.max(np.abs(trace)) > channel_max: channel_max = np.max(np.abs(trace)) argmax = np.argmax(np.abs(trace)) imin = np.int(max(argmax - 50 * sampling_rate, 0)) imax = np.int(argmax + 50 * sampling_rate) for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) if compare == 'trace': tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) ** 2) / noise_RMS ** 2 elif compare == 'abs': tmp = np.sum(np.abs(np.abs(trace[imin:imax]) - np.abs(np.roll(analytic_traces[iCh], pos)[imin:imax])) ** 2) / noise_RMS ** 2 elif compare == 'hilbert': tmp = np.sum(np.abs(np.abs(signal.hilbert(trace[imin:imax])) - np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos)[imin:imax]))) ** 2) / noise_RMS ** 2 else: raise NameError('Unsupported value for parameter "compare": {}. Value must be "trace", "abs" or "hilbert".'.format(compare)) chi2 += tmp if(debug_obj and self.i_slope_fit_iterations % 50 == 0): ax[iCh][1].plot(np.array(trace) / units.mV, label='measurement', color='blue') ax[iCh][1].plot(np.roll(analytic_traces[iCh], pos) / units.mV, '--', label='fit', color='orange') ax[iCh][1].plot(np.abs(signal.hilbert(trace)) / units.mV, linestyle=':', color='blue') ax[iCh][1].plot(np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos))) / units.mV, ':', label='fit', color='orange') ax[iCh][0].plot(np.abs(fft.time2freq(trace, sampling_rate)) / units.mV, label='measurement') ax[iCh][0].plot(np.abs(fft.time2freq(np.roll(analytic_traces[iCh], pos), sampling_rate)) / units.mV, '--', label='fit') ax[iCh][0].set_xlim([0, 600]) ax[iCh][1].set_xlim([imin - 500, imax + 500]) ax[iCh][1].axvline(imin, linestyle='--', alpha=.8) ax[iCh][1].axvline(imax, linestyle='--', alpha=.8) if(debug_obj and self.i_slope_fit_iterations % 50 == 0): sim_channel = station.get_sim_station().get_channel(0)[0] ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampTheta, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange') ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(station.get_sim_station().get_channel(0)[0].get_frequency_spectrum()[1]), color='blue') ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampPhi, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange') ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(sim_channel.get_frequency_spectrum()[2]), color='blue') ax[4][0].set_xlim([20, 500]) ax[4][1].set_xlim([20, 500]) logger.debug("amp phi = {:.4g}, amp theta = {:.4g}, slope = {:.4g} chi2 = {:.8g}".format(ampPhi, ampTheta, slope, chi2)) if(debug_obj and self.i_slope_fit_iterations % 50 == 0): fig.tight_layout() plt.show() self.i_slope_fit_iterations = 0 self.i_slope_fit_iterations += 1 return chi2
def run(self, evt, station, det): t = time.time() # access simulated efield and high level parameters sim_station = station.get_sim_station() sim_station_id = sim_station.get_id() # loop over all channels for efield in sim_station.get_electric_fields(): # one efield might be valid for multiple channels, hence we loop over all channels this efiels is valid for, # convolve each trace with the antenna response for the given angles # and transform it to the time domain to calculate the max. amplitude channel_ids = efield.get_channel_ids() for channel_id in channel_ids: logger.debug('channel id {}'.format(channel_id)) zenith = efield[efp.zenith] azimuth = efield[efp.azimuth] ff = efield.get_frequencies() efield_fft = efield.get_frequency_spectrum() # get antenna pattern for current channel antenna_model = det.get_antenna_model(sim_station_id, channel_id, zenith) antenna_pattern = self.antenna_provider.load_antenna_pattern( antenna_model, interpolation_method='complex') ori = det.get_antenna_orientation(sim_station_id, channel_id) logger.debug("zen {:.0f}, az {:.0f}".format( zenith / units.deg, azimuth / units.deg)) VEL = antenna_pattern.get_antenna_response_vectorized( ff, zenith, azimuth, *ori) # Apply antenna response to electric field voltage_fft = efield_fft[2] * VEL['phi'] + efield_fft[1] * VEL[ 'theta'] # Remove DC offset voltage_fft[np.where(ff < 5 * units.MHz)] = 0. voltage = fft.freq2time(voltage_fft, efield.get_sampling_rate()) h = np.abs(signal.hilbert(voltage)) maximum = np.abs(voltage).max() maximum_envelope = h.max() if not efield.has_parameter(efp.max_amp_antenna): efield[efp.max_amp_antenna] = {} efield[efp.max_amp_antenna_envelope] = {} efield[efp.max_amp_antenna][channel_id] = maximum efield[efp. max_amp_antenna_envelope][channel_id] = maximum_envelope self.__t += time.time() - t
def fold_efields(efield, zenith, azimuth, antenna_orientation, antenna_pattern): """ A function to do fold efields with the antenna response Apply the complex response of the antenna (the vector effective length) to an efield, and return the efield after the antenna Parameters ---------- signal: icetradio.I3EField the efield at the antenna zenith: float the zenith angle (in radians!) of the signal incident on the antenna azimuth: float the azimuth angle (in radians!) of the signal incident on the antenna antenna_orientation: array array of floats, specifically the orientation_theta, orientation_phi, rotation_theta, and rotation_phi, as they are defined in the NuRadioReco framework see https://nu-radio.github.io/NuRadioReco/pages/detector_database_fields.html#antenna-table or also the definitions in I3IceAntennaGeo https://code.icecube.wisc.edu/projects/icecube/browser/IceCube/sandbox/brianclark/ehe/radio/trunk/dataclasses/public/dataclasses/geometry/I3IceAntennaGeo.h antenna_pattern: NuRadioReco.detector.antennapattern the antenna pattern for this antenna Returns ------- trace: the voltage trace that will be observed after being folded with the antenna """ # get the frequencies where the efield needs to be evaluated ff = util_dataclasses.get_frequencies_I3EField(efield) # get the fourier transforms of the field eTheta_freq = fft.time2freq(efield.eTheta.trace, efield.eTheta.samplingRate) ePhi_freq = fft.time2freq(efield.ePhi.trace, efield.ePhi.samplingRate) # get the vector effective length (VEL) antenna_response = antenna_pattern.get_antenna_response_vectorized( ff, zenith, azimuth, *antenna_orientation) VEL = np.array([antenna_response['theta'], antenna_response['phi']]) voltage_fft = np.sum(VEL * np.array([eTheta_freq, ePhi_freq]), axis=0) # we need to make sure to cancel out the DC offset voltage_fft[np.where(ff < 5 * units.MHz)] = 0. voltage_trace = fft.freq2time(voltage_fft, efield.eR.samplingRate) return voltage_trace
def apply_amplifier_filter(voltage_trace, dT, amplifier_filter_response): """ A function to apply amplifier+filter responses to a voltage trace Apply the complex response of the amplifier and filter (magnitude and phase) to a voltage trace, and return the trace after amplification Parameters ---------- voltage_trace: array the trace to which we want to apply the amplifier and filter dT: float the time between samples of the voltage trace azimuth: float the azimuth angle (in radians!) of the signal incident on the antenna amplifier_filter_response: array The dict containing the amplifier + filter response As loaded in the load_filter_amplifier_response function Returns ------- trace: the voltage trace that will be observed after applying the amps + filters """ orig_frequencies = amplifier_filter_response['frequencies'] orig_phase = amplifier_filter_response['phase'] orig_gain = amplifier_filter_response['gain'] # interpolate the phase and gain interp_phase = interp1d(orig_frequencies, np.unwrap(orig_phase), bounds_error=False, fill_value=0) interp_gain = interp1d(orig_frequencies, orig_gain, bounds_error=False, fill_value=0) num_samples = len(voltage_trace) # the number of samples frequencies = np.fft.rfftfreq(num_samples, dT) gain = interp_gain(frequencies) phase = np.exp(1j * interp_phase(frequencies)) the_fft = fft.time2freq(voltage_trace, 1. / dT) the_fft *= (gain * phase) the_result_trace = fft.freq2time(the_fft, 1. / dT) return the_result_trace
def tunnel_diode(self, channel): """ Calculate a signal as processed by the tunnel diode. The given signal is convolved with the tunnel diode response as in AraSim. The diode model used in this module returns a dimensionless power trace, where the antenna resistance is only a normalisation for the final voltage. That's why the antenna resistance has been fixed to a value of 8.5 ohms. Parameters ---------- channel: Channel Signal to be processed by the tunnel diode. Returns ------- trace_after_tunnel_diode: array Signal output of the tunnel diode for the input `channel`. Careful! This trace is dimensionless and comes from a convolution of the power with the diode response. """ t_max = 1e-7 * units.s antenna_resistance = 8.5 * units.ohm n_pts = int(t_max * channel.get_sampling_rate()) times = np.linspace(0, t_max, n_pts + 1) diode_resp = self._td_fdown1(times) + self._td_fdown2(times) t_slice = times > self._td_args['up'][1] diode_resp[t_slice] += self._td_fup(times[t_slice]) conv = scipy.signal.convolve(channel.get_trace() ** 2 / antenna_resistance, diode_resp, mode='full') # conv multiplied by dt so that the amplitude stays constant for # varying dts (determined emperically, see ARVZAskaryanSignal comments) # Setting output trace_after_tunnel_diode = conv / channel.get_sampling_rate() trace_after_tunnel_diode = trace_after_tunnel_diode[:channel.get_trace().shape[0]] # We filter the output if the band is specified if self._output_passband != (None, None): sampling_rate = channel.get_sampling_rate() trace_spectrum = time2freq(trace_after_tunnel_diode, sampling_rate) frequencies = np.linspace(0, sampling_rate / 2, len(trace_spectrum)) if self._output_passband[0] is None: b, a = butter(6, self._output_passband[1], 'lowpass', analog=True) else: b, a = butter(6, self._output_passband, 'bandpass', analog=True) w, h = freqs(b, a, frequencies) trace_after_tunnel_diode = freq2time(h * trace_spectrum, sampling_rate) return trace_after_tunnel_diode
def get_trace(self): """ returns the time trace. If the frequency spectrum was modified before, an ifft is performed automatically to have the time domain representation up to date. Returns: 1 or N dimensional np.array of floats the time trace """ if(not self.__time_domain_up_to_date): self._time_trace = fft.freq2time(self._frequency_spectrum, self._sampling_rate) self.__time_domain_up_to_date = True self._frequency_spectrum = None return np.copy(self._time_trace)
def loop(zipped): threshold = float(zipped[0]) seed = int(zipped[1]) station = NuRadioReco.framework.station.Station(station_id) evt = NuRadioReco.framework.event.Event(0, 0) channelGenericNoiseAdder = NuRadioReco.modules.channelGenericNoiseAdder.channelGenericNoiseAdder( ) channelGenericNoiseAdder.begin(seed=seed) for channel_id in channel_ids: # Noise rms is amplified to greater than Vrms so that, after filtering, its the Vrms we expect spectrum = channelGenericNoiseAdder.bandlimited_noise( min_freq, max_freq, n_samples, sampling_rate, amplitude, type="rayleigh", time_domain=False) trace = fft.freq2time(spectrum * filt, sampling_rate) channel = NuRadioReco.framework.channel.Channel(channel_id) channel.set_trace(trace, sampling_rate) station.add_channel(channel) threshold_ = threshold * np.power(Vrms, 2.0) triggered = triggerSimulator.run(evt, station, det, Vrms, threshold_, triggered_channels=channels, phasing_angles=default_angles, ref_index=1.75, trigger_name='primary_phasing', trigger_adc=False, adc_output='voltage', trigger_filter=None, upsampling_factor=upsampling_factor, window=window_length, step=step_length) return triggered
def get_filtered_trace(self, passband, filter_type='butter', order=10): """ Returns the trace after applying a filter to it. This does not change the stored trace. Parameters: -------------- passband: list of floats lower and upper bound of the filter passband filter_type: string type of the applied filter. Options are rectangular, butter and butterabs order: int Order of the Butterworth filter, if the filter types butter or butterabs are chosen """ spec = copy.copy(self.get_frequency_spectrum()) freq = self.get_frequencies() filter_response = bandpass_filter.get_filter_response(freq, passband, filter_type, order) spec *= filter_response return fft.freq2time(spec, self.get_sampling_rate())
def delay_trace(trace, sampling_frequency, time_delay, delayed_samples): """ Delays a trace by transforming it to frequency and multiplying by phases. Since this method is cyclic, the trace has to be cropped. It only accepts positive delays, so some samples from the beginning are thrown away and then some samples from the end so that the total number of samples is equal to the argument delayed samples. Parameters ---------- trace: array of floats Array containing the trace sampling_frequency: float Sampling rate for the trace time_delay: float Time delay used for transforming the trace. Must be positive or 0 delayed_samples: integer Number of samples that the delayed trace must contain Returns ------- delayed_trace: array of floats The delayed, cropped trace """ if time_delay < 0: msg = 'Time delay must be positive' raise ValueError(msg) n_samples = len(trace) spectrum = fft.time2freq(trace, sampling_frequency) frequencies = np.fft.rfftfreq(n_samples, 1 / sampling_frequency) spectrum *= np.exp(-1j * 2 * np.pi * frequencies * time_delay) delayed_trace = fft.freq2time(spectrum, sampling_frequency) init_sample = int(time_delay * sampling_frequency) + 1 delayed_trace = delayed_trace[init_sample:None] delayed_trace = delayed_trace[:delayed_samples] return delayed_trace
def __calculate_time_delays_amp(self, amp_type): """ helper function to calculate the time delay of the amp for a delta pulse """ amp_response_f = analog_components.load_amp_response(amp_type) sampling_rate = 10 * units.GHz # assume a huge sampling rate to have a good time resolution n = 2 ** 12 trace = np.zeros(n) trace[n // 2] = 1 max_time = trace.argmax() / sampling_rate spec = fft.time2freq(trace, sampling_rate) ff = np.fft.rfftfreq(n, 1. / sampling_rate) amp_response_gain = amp_response_f['gain'](ff) amp_response_phase = amp_response_f['phase'](ff) mask = (ff < 70 * units.MHz) & (ff > 40 * units.MHz) spec[~mask] = 0 trace2 = fft.freq2time(spec * amp_response_gain * amp_response_phase, sampling_rate) max_time2 = np.abs(trace2).argmax() / sampling_rate return max_time2 - max_time
def __calculate_time_delays_cable(self): """ helper function to calculate the time delay of the amp for a delta pulse """ sampling_rate = 10 * units.GHz # assume a huge sampling rate to have a good time resolution n = 2 ** 12 trace = np.zeros(n) trace[n // 2] = 1 max_time = trace.argmax() / sampling_rate spec = fft.time2freq(trace, sampling_rate) ff = np.fft.rfftfreq(n, 1. / sampling_rate) response = analog_components.get_cable_response(ff) response_gain = response['gain'] response_phase = response['phase'] trace2 = fft.freq2time(spec * response_gain * response_phase, sampling_rate) # import matplotlib.pyplot as plt # fig, ax = plt.subplots(1, 1) # ax.plot(trace) # ax.plot(trace2) # plt.show() max_time2 = np.abs(trace2).argmax() / sampling_rate return max_time2 - max_time
def get_analytic_pulse(amp_p0, amp_p1, phase_p0, n_samples_time, sampling_rate, phase_p1=0, bandpass=None, quadratic_term=0, quadratic_term_offset=0): """ Analytic pulse as described in PhD thesis Glaser and NuRadioReco paper in the time domain Parameters ---------- amp_p0: float amplitude parameter of analytic pulse amp_p1: slope parameter of analytic pulse phase_p0: phase parameter of analytic pulse n_samples_time: numer of samples in time-domain sampling_rate: sampling rate of trace phase_p1: default 0 bandpass: default None quadratic_term: default 0 quadratic_term_offset: default 0 """ xx = get_analytic_pulse_freq(amp_p0, amp_p1, phase_p0, n_samples_time, sampling_rate, phase_p1=phase_p1, bandpass=bandpass, quadratic_term=quadratic_term, quadratic_term_offset=quadratic_term_offset) return fft.freq2time(xx, sampling_rate)
def obj_amplitude(params, slope, phase, pos, debug_obj=0): if(len(params) == 2): ampPhi, ampTheta = params elif(len(params) == 1): ampPhi = params[0] ampTheta = 0 analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) chi2 = 0 if(debug_obj): fig, ax = plt.subplots(4, 2, sharex=True) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) # first determine the position with the larges xcorr for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) argmax = np.argmax(np.abs(trace)) imin = np.int(argmax - 30 * sampling_rate) imax = np.int(argmax + 50 * sampling_rate) tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) / noise_RMS) chi2 += tmp ** 2 if(debug_obj): ax[iCh][0].plot(trace, label='measurement') ax[iCh][0].plot(np.roll(analytic_traces[iCh], pos), '--', label='fit') ax[iCh][1].plot(trace - np.roll(analytic_traces[iCh], pos), label='delta') ax[iCh][1].set_xlim(imin, imax) logger.debug("amp phi = {:.4g}, amp theta = {:.4g} , chi2 = {:.2g}".format(ampPhi, ampTheta, chi2)) if(debug_obj): fig.suptitle("amp phi = {:.4g}, amp theta = {:.4g} , chi2 = {:.2g}".format(ampPhi, ampTheta, chi2)) fig.tight_layout() plt.show() return chi2
def generate_signal(deposited_energy, shower_axis, em_or_had, launch_vector, distance, arrival_time, n_index, attenuation_values, dt, n_samples, model, seed, keep_unattenuated_fields=False): """ A function to generate askaryan fields at the antennas Get the askaryan signals/fields at the antenna. This means that the fields returned by this function will already include the polarization factors, the 1/R, and the attenuation due to the ice. Parameters ---------- deposited_energy: double or float energy deposited in the shower in eV shower_axis: I3Position the shower axis launch_vector: I3Position the launch vector of the ray that makes the signal distance: float the path length traveled by the signal in m (including ray bending!) arrival_time: float the time the field arrives at the antenna, in seconds (including ray bending!) n_index: float the index of refraction at the vertex atttenuation_values: complex np array the complex frequency-dependent attenuation factors dt: float the time between samples for the askaryan emission, in seconds n_samples: int the number of samples to have in the Askaryan emission model: string what Askaryan model should be used to generate the emission options are described in NuRadioMC.SignalGen.askaryan https://github.com/nu-radio/NuRadioMC/blob/master/NuRadioMC/SignalGen/askaryan.py seed: int what random number seed should be used in generating the askaryan emission keep_unattenuated_fields: bool whether or not to keep a copy of the E-fields that does not have attenuation factors applied default is False, to reduce file output sizes Returns ------- signal: I3RadioSignal the radio signal container for this event """ local_launch_vector = util_dataclasses.i3pos_to_np(launch_vector) local_shower_axis = util_dataclasses.i3pos_to_np(shower_axis) viewing_angle = hp.get_angle(local_shower_axis, local_launch_vector) signal = askaryan.get_time_trace(energy=deposited_energy, theta=viewing_angle, N=n_samples, dt=dt, shower_type=em_or_had, n_index=n_index, R=distance, model=model, seed=seed) signal_spectrum = fft.time2freq(signal, 1. / dt) attenuated_signal_spectrum = signal_spectrum * attenuation_values attenuated_signal = fft.freq2time(attenuated_signal_spectrum, 1. / dt) # calculate the polarization polarization_direction_onsky = util_geo.calculate_polarization_vector( local_launch_vector, local_shower_axis) icetray.logging.log_debug("Polarization direction on sky {}".format( polarization_direction_onsky)) # create the e-fields at the antenna this_eR_attenuated, this_eTheta_attenuated, this_ePhi_attenuated = np.outer( polarization_direction_onsky, attenuated_signal) # store the eR, eTheta, ePhi components in trace for attenuated field sampling_rate = 1. / dt eR_attenuated = util_dataclasses.fill_I3Trace(this_eR_attenuated, arrival_time, sampling_rate) eTheta_attenuated = util_dataclasses.fill_I3Trace(this_eTheta_attenuated, arrival_time, sampling_rate) ePhi_attenuated = util_dataclasses.fill_I3Trace(this_ePhi_attenuated, arrival_time, sampling_rate) # put those traces into fields field_watt = util_dataclasses.fill_I3EField(eR_attenuated, eTheta_attenuated, ePhi_attenuated) # and finally, create and return a signal object signal = icetradio.I3RadioSignal() signal.view_angle = viewing_angle * icetray.I3Units.rad signal.polarization_vector = util_dataclasses.np_to_i3pos( polarization_direction_onsky, 'sph') signal.field_watt = field_watt if keep_unattenuated_fields: # make a copy of the fields that doesn't include the attenuation factor # we can generally *not* save this information as a space saving measure this_eR, this_eTheta, this_ePhi = np.outer( polarization_direction_onsky, signal) eR = util_dataclasses.fill_I3Trace(this_eR, arrival_time, sampling_rate) eTheta = util_dataclasses.fill_I3Trace(this_eTheta, arrival_time, sampling_rate) ePhi = util_dataclasses.fill_I3Trace(this_ePhi, arrival_time, sampling_rate) field_noatt = util_dataclasses.fill_I3EField(eR, eTheta, ePhi) signal.field_noatt = field_noatt return signal
def run(self, evt, station, det, debug=False, debug_plotpath=None, use_channels=None, bandpass=None, use_MC_direction=False): """ run method. This function is executed for each event Parameters --------- evt station det debug: bool if True debug plotting is enables debug_plotpath: string or None if not None plots will be saved to a file rather then shown. Plots will be save into the `debug_plotpath` directory use_channels: array of ints (default: [0, 1, 2, 3]) the channel ids to use for the electric field reconstruction default: 0 - 3 bandpass: [float, float] (default: [100 * units.MHz, 500 * units.MHz]) the lower and upper frequecy for which the analytic pulse is calculated. A butterworth filter of 10th order and a rectangular filter is applied. default 100 - 500 MHz use_MC_direction: bool use simulated direction instead of reconstructed direction """ if use_channels is None: use_channels = [0, 1, 2, 3] if bandpass is None: bandpass = [100 * units.MHz, 500 * units.MHz] self.__counter += 1 station_id = station.get_id() logger.info("event {}, station {}".format(evt.get_id(), station_id)) if use_MC_direction and (station.get_sim_station() is not None): zenith = station.get_sim_station()[stnp.zenith] azimuth = station.get_sim_station()[stnp.azimuth] sim_present = True else: logger.warning("Using reconstructed angles as no simulation present") zenith = station[stnp.zenith] azimuth = station[stnp.azimuth] sim_present = False efield_antenna_factor, V, V_timedomain = get_array_of_channels(station, use_channels, det, zenith, azimuth, self.antenna_provider, time_domain=True) sampling_rate = station.get_channel(0).get_sampling_rate() n_samples_time = V_timedomain.shape[1] noise_RMS = det.get_noise_RMS(station.get_id(), 0) def obj_xcorr(params): if(len(params) == 3): slope, ratio2, phase2 = params ratio = (np.arctan(ratio2) + np.pi * 0.5) / np.pi # project -inf..inf on 0..1 elif(len(params) == 2): slope, ratio2 = params phase2 = 0 ratio = (np.arctan(ratio2) + np.pi * 0.5) / np.pi # project -inf..inf on 0..1 elif(len(params) == 1): phase2 = 0 ratio = 0 slope = params[0] phase = np.arctan(phase2) # project -inf..+inf to -0.5 pi..0.5 pi analytic_pulse_theta = pulse.get_analytic_pulse_freq(ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse_freq(1 - ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) chi2 = 0 n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) positions = np.zeros(n_channels, dtype=np.int) max_xcorrs = np.zeros(n_channels) # first determine the position with the larges xcorr for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) xcorr = np.abs(hp.get_normalized_xcorr(trace, analytic_traces[iCh])) positions[iCh] = np.argmax(np.abs(xcorr)) + 1 max_xcorrs[iCh] = xcorr.max() chi2 -= xcorr.max() logger.debug("ratio = {:.2f}, slope = {:.4g}, phase = {:.0f} ({:.4f}), chi2 = {:.4g}".format(ratio, slope, phase / units.deg, phase2, chi2)) return chi2 def obj_amplitude(params, slope, phase, pos, debug_obj=0): if(len(params) == 2): ampPhi, ampTheta = params elif(len(params) == 1): ampPhi = params[0] ampTheta = 0 analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) chi2 = 0 if(debug_obj): fig, ax = plt.subplots(4, 2, sharex=True) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) # first determine the position with the larges xcorr for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) argmax = np.argmax(np.abs(trace)) imin = np.int(argmax - 30 * sampling_rate) imax = np.int(argmax + 50 * sampling_rate) tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) / noise_RMS) chi2 += tmp ** 2 if(debug_obj): ax[iCh][0].plot(trace, label='measurement') ax[iCh][0].plot(np.roll(analytic_traces[iCh], pos), '--', label='fit') ax[iCh][1].plot(trace - np.roll(analytic_traces[iCh], pos), label='delta') ax[iCh][1].set_xlim(imin, imax) logger.debug("amp phi = {:.4g}, amp theta = {:.4g} , chi2 = {:.2g}".format(ampPhi, ampTheta, chi2)) if(debug_obj): fig.suptitle("amp phi = {:.4g}, amp theta = {:.4g} , chi2 = {:.2g}".format(ampPhi, ampTheta, chi2)) fig.tight_layout() plt.show() return chi2 def obj_amplitude_slope(params, phase, pos, compare='hilbert', debug_obj=0): ampPhi, ampTheta, slope = params analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) chi2 = 0 if(debug_obj and self.i_slope_fit_iterations % 25 == 0): fig, ax = plt.subplots(4, 2, sharex=False, figsize=(20, 10)) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) # first determine the position with the larges xcorr channel_max = 0 for trace in V_timedomain: if np.max(np.abs(trace)) > channel_max: channel_max = np.max(np.abs(trace)) argmax = np.argmax(np.abs(trace)) imin = np.int(max(argmax - 50 * sampling_rate, 0)) imax = np.int(argmax + 50 * sampling_rate) for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) if compare == 'trace': tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) ** 2) / noise_RMS ** 2 elif compare == 'abs': tmp = np.sum(np.abs(np.abs(trace[imin:imax]) - np.abs(np.roll(analytic_traces[iCh], pos)[imin:imax])) ** 2) / noise_RMS ** 2 elif compare == 'hilbert': tmp = np.sum(np.abs(np.abs(signal.hilbert(trace[imin:imax])) - np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos)[imin:imax]))) ** 2) / noise_RMS ** 2 else: raise NameError('Unsupported value for parameter "compare": {}. Value must be "trace", "abs" or "hilbert".'.format(compare)) chi2 += tmp if(debug_obj and self.i_slope_fit_iterations % 25 == 0): ax[iCh][1].plot(np.array(trace) / units.mV, label='measurement', color='blue') ax[iCh][1].plot(np.roll(analytic_traces[iCh], pos) / units.mV, '--', label='fit', color='orange') ax[iCh][1].plot(np.abs(signal.hilbert(trace)) / units.mV, linestyle=':', color='blue') ax[iCh][1].plot(np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos))) / units.mV, ':', label='fit', color='orange') # ax[iCh][1].plot(trace - np.roll(analytic_traces[iCh], pos), label='delta') ax[iCh][0].plot(np.abs(fft.time2freq(trace, sampling_rate)) / units.mV, label='measurement') ax[iCh][0].plot(np.abs(fft.time2freq(np.roll(analytic_traces[iCh], pos), sampling_rate)) / units.mV, '--', label='fit') ax[iCh][0].set_xlim([0, 600]) ax[iCh][1].set_xlim([imin - 500, imax + 500]) ax[iCh][1].axvline(imin, linestyle='--', alpha=.8) ax[iCh][1].axvline(imax, linestyle='--', alpha=.8) logger.debug("amp phi = {:.4g}, amp theta = {:.4g}, slope = {:.4g} chi2 = {:.8g}".format(ampPhi, ampTheta, slope, chi2)) if(debug_obj and self.i_slope_fit_iterations % 25 == 0): fig.tight_layout() plt.show() self.i_slope_fit_iterations = 0 self.i_slope_fit_iterations += 1 return chi2 def obj_amplitude_second_order(params, slope, phase, pos, compare='hilbert', debug_obj=0): ampPhi, ampTheta, second_order = params analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0]) analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0]) chi2 = 0 if(debug_obj and self.i_slope_fit_iterations % 50 == 0): fig, ax = plt.subplots(5, 2, sharex=False, figsize=(20, 10)) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) # first determine the position with the larges xcorr channel_max = 0 for trace in V_timedomain: if np.max(np.abs(trace)) > channel_max: channel_max = np.max(np.abs(trace)) argmax = np.argmax(np.abs(trace)) imin = np.int(max(argmax - 50 * sampling_rate, 0)) imax = np.int(argmax + 50 * sampling_rate) for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) if compare == 'trace': tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) ** 2) / noise_RMS ** 2 elif compare == 'abs': tmp = np.sum(np.abs(np.abs(trace[imin:imax]) - np.abs(np.roll(analytic_traces[iCh], pos)[imin:imax])) ** 2) / noise_RMS ** 2 elif compare == 'hilbert': tmp = np.sum(np.abs(np.abs(signal.hilbert(trace[imin:imax])) - np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos)[imin:imax]))) ** 2) / noise_RMS ** 2 else: raise NameError('Unsupported value for parameter "compare": {}. Value must be "trace", "abs" or "hilbert".'.format(compare)) chi2 += tmp if(debug_obj and self.i_slope_fit_iterations % 50 == 0): ax[iCh][1].plot(np.array(trace) / units.mV, label='measurement', color='blue') ax[iCh][1].plot(np.roll(analytic_traces[iCh], pos) / units.mV, '--', label='fit', color='orange') ax[iCh][1].plot(np.abs(signal.hilbert(trace)) / units.mV, linestyle=':', color='blue') ax[iCh][1].plot(np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos))) / units.mV, ':', label='fit', color='orange') ax[iCh][0].plot(np.abs(fft.time2freq(trace, sampling_rate)) / units.mV, label='measurement') ax[iCh][0].plot(np.abs(fft.time2freq(np.roll(analytic_traces[iCh], pos), sampling_rate)) / units.mV, '--', label='fit') ax[iCh][0].set_xlim([0, 600]) ax[iCh][1].set_xlim([imin - 500, imax + 500]) ax[iCh][1].axvline(imin, linestyle='--', alpha=.8) ax[iCh][1].axvline(imax, linestyle='--', alpha=.8) if(debug_obj and self.i_slope_fit_iterations % 50 == 0): sim_channel = station.get_sim_station().get_channel(0)[0] ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampTheta, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange') ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(station.get_sim_station().get_channel(0)[0].get_frequency_spectrum()[1]), color='blue') ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampPhi, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange') ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(sim_channel.get_frequency_spectrum()[2]), color='blue') ax[4][0].set_xlim([20, 500]) ax[4][1].set_xlim([20, 500]) logger.debug("amp phi = {:.4g}, amp theta = {:.4g}, slope = {:.4g} chi2 = {:.8g}".format(ampPhi, ampTheta, slope, chi2)) if(debug_obj and self.i_slope_fit_iterations % 50 == 0): fig.tight_layout() plt.show() self.i_slope_fit_iterations = 0 self.i_slope_fit_iterations += 1 return chi2 method = "Nelder-Mead" options = {'maxiter': 1000, 'disp': True} res = opt.minimize(obj_xcorr, x0=[-1], method=method, options=options) logger.info("slope xcorr fit, slope = {:.3g} with fmin = {:.3f}".format(res.x[0], res.fun)) # plot objective function phase = 0 ratio = 0 slope = res.x[0] if slope > 0 or slope < -50: # sanity check slope = -1.9 analytic_pulse_theta_freq = pulse.get_analytic_pulse_freq(ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi_freq = pulse.get_analytic_pulse_freq(1 - ratio, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) n_channels = len(V_timedomain) analytic_traces = np.zeros((n_channels, n_samples_time)) positions = np.zeros(n_channels, dtype=np.int) max_xcorrs = np.zeros(n_channels) for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta_freq, analytic_pulse_phi_freq]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate) xcorr = np.abs(hp.get_normalized_xcorr(trace, analytic_traces[iCh])) positions[iCh] = np.argmax(np.abs(xcorr)) + 1 max_xcorrs[iCh] = xcorr.max() pos = positions[np.argmax(max_xcorrs)] for iCh, trace in enumerate(V_timedomain): analytic_traces[iCh] = np.roll(analytic_traces[iCh], pos) res_amp = opt.minimize(obj_amplitude, x0=[1.], args=(slope, phase, pos, 0), method=method, options=options) logger.info("amplitude fit, Aphi = {:.3g} with fmin = {:.5e}".format(res_amp.x[0], res_amp.fun)) res_amp = opt.minimize(obj_amplitude, x0=[res_amp.x[0], 0], args=(slope, phase, pos, 0), method=method, options=options) logger.info("amplitude fit, Aphi = {:.3g} Atheta = {:.3g} with fmin = {:.5e}".format(res_amp.x[0], res_amp.x[1], res_amp.fun)) # counts number of iterations in the slope fit. Used so we do not need to show the plots every iteration self.i_slope_fit_iterations = 0 res_amp_slope = opt.minimize(obj_amplitude_slope, x0=[res_amp.x[0], res_amp.x[1], slope], args=(phase, pos, 'hilbert', False), method=method, options=options) # calculate uncertainties def Wrapper(params): return obj_amplitude_slope(params, phase, pos, 0) try: cov = covariance(Wrapper, res_amp_slope.x, 0.5, fast=True) except: cov = np.zeros((3, 3)) logger.info("slope fit, Aphi = {:.3g}+-{:.3g} Atheta = {:.3g}+-{:.3g}, slope = {:.3g}+-{:.3g} with fmin = {:.5e}".format( res_amp_slope.x[0], cov[0, 0] ** 0.5, res_amp_slope.x[1], cov[1, 1] ** 0.5, res_amp_slope.x[2], cov[2, 2] ** 0.5, res_amp_slope.fun) ) logger.info("covariance matrix \n{}".format(cov)) if(cov[0, 0] > 0 and cov[1, 1] > 0 and cov[2, 2] > 0): logger.info("correlation matrix \n{}".format(hp.covariance_to_correlation(cov))) Aphi = res_amp_slope.x[0] Atheta = res_amp_slope.x[1] slope = res_amp_slope.x[2] Aphi_error = cov[0, 0] ** 0.5 Atheta_error = cov[1, 1] ** 0.5 # plot objective function if 0: fo, ao = plt.subplots(1, 1) ss = np.linspace(-6, -0, 100) oos = [obj_amplitude_slope([res_amp_slope.x[0], res_amp_slope.x[1], s], phase, pos) for s in ss] ao.plot(ss, oos) n = 10 x = np.linspace(res_amp_slope.x[0] * 0.6, res_amp_slope.x[0] * 1.4, n) y = np.linspace(-5, -1, n) X, Y = np.meshgrid(x, y) Z = np.zeros((n, n)) for i in range(n): for j in range(n): Z[i, j] = obj_amplitude_slope([X[i, j], X[i, j] * res_amp_slope.x[1] / res_amp_slope.x[0], Y[i, j]], phase, pos) fig, ax = plt.subplots(1, 1) ax.pcolor(X, Y, Z, cmap='viridis_r', vmin=res_amp_slope.fun, vmax=res_amp_slope.fun * 2) analytic_pulse_theta = pulse.get_analytic_pulse(Atheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi = pulse.get_analytic_pulse(Aphi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_theta_freq = pulse.get_analytic_pulse_freq(Atheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_phi_freq = pulse.get_analytic_pulse_freq(Aphi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass) analytic_pulse_theta = np.roll(analytic_pulse_theta, pos) analytic_pulse_phi = np.roll(analytic_pulse_phi, pos) station_trace = np.array([np.zeros_like(analytic_pulse_theta), analytic_pulse_theta, analytic_pulse_phi]) electric_field = NuRadioReco.framework.electric_field.ElectricField(use_channels) electric_field.set_trace(station_trace, sampling_rate) energy_fluence = trace_utilities.get_electric_field_energy_fluence(electric_field.get_trace(), electric_field.get_times()) electric_field.set_parameter(efp.signal_energy_fluence, energy_fluence) electric_field.set_parameter_error(efp.signal_energy_fluence, np.array([0, Atheta_error, Aphi_error])) electric_field.set_parameter(efp.cr_spectrum_slope, slope) electric_field.set_parameter(efp.zenith, zenith) electric_field.set_parameter(efp.azimuth, azimuth) # calculate high level parameters x = np.sign(Atheta) * np.abs(Atheta) ** 0.5 y = np.sign(Aphi) * np.abs(Aphi) ** 0.5 sx = Atheta_error * 0.5 sy = Aphi_error * 0.5 pol_angle = np.arctan2(abs(y), abs(x)) pol_angle_error = 1. / (x ** 2 + y ** 2) * (y ** 2 * sx ** 2 + x ** 2 + sy ** 2) ** 0.5 # gaussian error propagation logger.info("polarization angle = {:.1f} +- {:.1f}".format(pol_angle / units.deg, pol_angle_error / units.deg)) electric_field.set_parameter(efp.polarization_angle, pol_angle) electric_field.set_parameter_error(efp.polarization_angle, pol_angle_error) # compute expeted polarization site = det.get_site(station.get_id()) exp_efield = hp.get_lorentzforce_vector(zenith, azimuth, hp.get_magnetic_field_vector(site)) cs = coordinatesystems.cstrafo(zenith, azimuth, site=site) exp_efield_onsky = cs.transform_from_ground_to_onsky(exp_efield) exp_pol_angle = np.arctan2(exp_efield_onsky[2], exp_efield_onsky[1]) logger.info("expected polarization angle = {:.1f}".format(exp_pol_angle / units.deg)) electric_field.set_parameter(efp.polarization_angle_expectation, exp_pol_angle) res_amp_second_order = opt.minimize( obj_amplitude_second_order, x0=[res_amp_slope.x[0], res_amp_slope.x[1], 0], args=(slope, phase, pos, 'hilbert', False), method=method, options=options ) second_order_correction = res_amp_second_order.x[2] electric_field.set_parameter(efp.cr_spectrum_quadratic_term, second_order_correction) # figure out the timing of the electric field voltages_from_efield = trace_utilities.get_channel_voltage_from_efield(station, electric_field, use_channels, det, zenith, azimuth, self.antenna_provider, False) correlation = np.zeros(voltages_from_efield.shape[1] + station.get_channel(use_channels[0]).get_trace().shape[0] - 1) channel_trace_start_times = [] for channel_id in use_channels: channel_trace_start_times.append(station.get_channel(channel_id).get_trace_start_time()) average_trace_start_time = np.average(channel_trace_start_times) for i_trace, v_trace in enumerate(voltages_from_efield): channel = station.get_channel(use_channels[i_trace]) time_shift = geo_utl.get_time_delay_from_direction(zenith, azimuth, det.get_relative_position(station.get_id(), use_channels[i_trace])) - (channel.get_trace_start_time() - average_trace_start_time) voltage_trace = np.roll(np.copy(v_trace), int(time_shift * electric_field.get_sampling_rate())) correlation += signal.correlate(voltage_trace, channel.get_trace()) toffset = (np.arange(0, correlation.shape[0]) - channel.get_trace().shape[0]) / electric_field.get_sampling_rate() electric_field.set_trace_start_time(-toffset[np.argmax(correlation)] + average_trace_start_time) station.add_electric_field(electric_field) if debug: analytic_traces = np.zeros((n_channels, n_samples_time)) for iCh, trace in enumerate(V_timedomain): analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta_freq, analytic_pulse_phi_freq]), axis=0) analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, electric_field.get_sampling_rate()) analytic_traces[iCh] = np.roll(analytic_traces[iCh], pos) fig, (ax2, ax2f) = plt.subplots(2, 1, figsize=(10, 8)) lw = 2 times = station.get_times() / units.ns ax2.plot(times, station.get_trace()[1] / units.mV * units.m, "-C0", label="analytic eTheta", lw=lw) ax2.plot(times, station.get_trace()[2] / units.mV * units.m, "-C1", label="analytic ePhi", lw=lw) tmax = times[np.argmax(station.get_trace()[2])] ax2.set_xlim(tmax - 40, tmax + 50) ff = station.get_frequencies() / units.MHz df = ff[1] - ff[0] ax2f.plot(ff[ff < 600], np.abs(station.get_frequency_spectrum()[1][ff < 600]) / df / units.mV * units.m, "-C0", label="analytic eTheta", lw=lw) ax2f.plot(ff[ff < 600], np.abs(station.get_frequency_spectrum()[2][ff < 600]) / df / units.mV * units.m, "-C1", label="analytic ePhi", lw=lw) if station.has_sim_station(): sim_station = station.get_sim_station() logger.debug("station start time {:.1f}ns, relativ sim station time = {:.1f}".format(station.get_trace_start_time(), sim_station.get_trace_start_time())) df = (sim_station.get_frequencies()[1] - sim_station.get_frequencies()[0]) / units.MHz c = 1. ffsim = sim_station.get_frequencies() mask = (ffsim > 100 * units.MHz) & (ffsim < 500 * units.MHz) result = poly.polyfit(ffsim[mask], np.log10(np.abs(sim_station.get_frequency_spectrum()[2][mask]) / df / units.mV * units.m), 1, full=True) logger.info("polyfit result = {:.2g} {:.2g}".format(*result[0])) ax2.plot(sim_station.get_times() / units.ns, sim_station.get_trace()[1] / units.mV * units.m * c, "--C0", label="simulation eTheta", lw=lw) ax2.plot(sim_station.get_times() / units.ns, sim_station.get_trace()[2] / units.mV * units.m * c, "--C1", label="simulation ePhi", lw=lw) ax2f.plot(sim_station.get_frequencies() / units.MHz, np.abs(sim_station.get_frequency_spectrum()[1]) / df / units.mV * units.m * c, "--C0", label="simulation eTheta", lw=lw) ax2f.plot(sim_station.get_frequencies() / units.MHz, np.abs(sim_station.get_frequency_spectrum()[2]) / df / units.mV * units.m * c, "--C1", label="simulation ePhi", lw=lw) ax2f.plot(ffsim / units.MHz, 10 ** (result[0][0] + result[0][1] * ffsim), "C3:") ax2.legend(fontsize="xx-small") ax2.set_xlabel("time [ns]") ax2.set_ylabel("electric-field [mV/m]") ax2f.set_ylim(1e-3, 5) ax2f.set_xlabel("Frequency [MHz]") ax2f.set_xlim(100, 500) ax2f.semilogy(True) if sim_present: sim = station.get_sim_station() fig.suptitle("Simulation: Zenith {:.1f}, Azimuth {:.1f}".format(np.rad2deg(sim[stnp.zenith]), np.rad2deg(sim[stnp.azimuth]))) else: fig.suptitle("Data: reconstructed zenith {:.1f}, azimuth {:.1f}".format(np.rad2deg(zenith), np.rad2deg(azimuth))) fig.tight_layout() fig.subplots_adjust(top=0.95) if(debug_plotpath is not None): fig.savefig(os.path.join(debug_plotpath, 'run_{:05d}_event_{:06d}_efield.png'.format(evt.get_run_number(), evt.get_id()))) plt.close(fig) # plot antenna response and channels fig, ax = plt.subplots(len(V), 3, sharex='col', sharey='col') for iCh in range(len(V)): mask = ff > 100 ax[iCh, 0].plot(ff[mask], np.abs(efield_antenna_factor[iCh][0])[mask], label="theta, channel {}".format(use_channels[iCh]), lw=lw) ax[iCh, 0].plot(ff[mask], np.abs(efield_antenna_factor[iCh][1])[mask], label="phi, channel {}".format(use_channels[iCh]), lw=lw) ax[iCh, 0].legend(fontsize='xx-small') ax[iCh, 0].set_xlim(100, 500) ax[iCh, 1].set_xlim(400, 600) ax[iCh, 2].set_xlim(400, 600) ax[iCh, 1].plot(times, V_timedomain[iCh] / units.micro / units.V, lw=lw) ax[iCh, 1].plot(times, analytic_traces[iCh] / units.micro / units.V, '--', lw=lw) ax[iCh, 2].plot(times, (V_timedomain[iCh] - analytic_traces[iCh]) / units.micro / units.V, '-', lw=lw) ax[iCh, 0].set_ylabel("H [m]") ax[iCh, 1].set_ylabel(r"V [$\mu$V]") ax[iCh, 2].set_ylabel(r"$\Delta$V [$\mu$V]") RMS = det.get_noise_RMS(station.get_id(), 0) ax[iCh, 1].text(0.6, 0.8, 'S/N={:.1f}'.format(np.max(np.abs(V_timedomain[iCh])) / RMS), transform=ax[iCh, 1].transAxes) ax[0][2].set_ylim(ax[0][1].get_ylim()) ax[-1, 1].set_xlabel("time [ns]") ax[-1, 2].set_xlabel("time [ns]") ax[-1, 0].set_xlabel("frequency [MHz]") fig.tight_layout() if(debug_plotpath is not None): fig.savefig(os.path.join(debug_plotpath, 'run_{:05d}_event_{:06d}_channels.png'.format(evt.get_run_number(), evt.get_id()))) plt.close(fig)
def bandlimited_noise(self, min_freq, max_freq, n_samples, sampling_rate, amplitude, type='perfect_white', time_domain=True, bandwidth=None): """ Generating noise of n_samples in a bandwidth [min_freq,max_freq]. Parameters --------- min_freq: float Minimum frequency of passband for noise generation min_freq = None: Only the DC component is removed. If the DC component should be included, min_freq = 0 has to be specified max_freq: float Maximum frequency of passband for noise generation If the maximum frequency is above the Nquist frequencey (0.5 * sampling rate), the Nquist frequency is used max_freq = None: Frequencies up to Nyquist freq are used. n_samples: int number of samples in the time domain sampling_rate: float desired sampling rate of data amplitude: float desired voltage of noise as V_rms (only roughly, since bandpass limited) type: string perfect_white: flat frequency spectrum rayleigh: Amplitude of each frequency bin is drawn from a Rayleigh distribution # white: flat frequency spectrum with random jitter time_domain: bool (default True) if True returns noise in the time domain, if False it returns the noise in the frequency domain. The latter might be more performant as the noise is generated internally in the frequency domain. bandwidth: float or None (default) if this parameter is specified, the amplitude is interpreted as the amplitude for the bandwidth specified here Otherwise the amplitude is interpreted for the bandwidth of min(max_freq, 0.5 * sampling rate) - min_freq If `bandwidth` is larger then (min(max_freq, 0.5 * sampling rate) - min_freq) it has the same effect as `None` Comments -------- * Note that by design the max frequency is the Nyquist frequency, even if a bigger max_freq is implemented (RL 17-Sept-2018) * Add 'multi_white' noise option on 20-Sept-2018 (RL) """ frequencies = np.fft.rfftfreq(n_samples, 1. / sampling_rate) n_samples_freq = len(frequencies) if min_freq is None or min_freq == 0: # remove DC component; fftfreq returns the DC component as 0-th element and the negative # frequencies at the end, so frequencies[1] should be the lowest frequency; it seems safer, # to take the difference between two frequencies to determine the minimum frequency, in case # future versions of numpy change the order and maybe put the negative frequencies first min_freq = 0.5 * (frequencies[2] - frequencies[1]) self.logger.info(' Set min_freq from None to {} MHz!'.format( min_freq / units.MHz)) if max_freq is None: # sample up to Nyquist frequency max_freq = max(frequencies) self.logger.info(' Set max_freq from None to {} GHz!'.format( max_freq / units.GHz)) selection = (frequencies >= min_freq) & (frequencies <= max_freq) nbinsactive = np.sum(selection) self.logger.debug( 'Total number of frequency bins (bilateral spectrum) : {} , of those active: {} ' .format(n_samples, nbinsactive)) # Debug plots # f1 = plt.figure() # plt.plot (frequencies/max(frequencies)) # plt.plot(fbinsactive,'kx') if (bandwidth is not None): sampling_bandwidth = min(0.5 * sampling_rate, max_freq) - min_freq amplitude *= 1. / ( bandwidth / (sampling_bandwidth) )**0.5 # normalize noise level to the bandwidth its generated for ampl = np.zeros(n_samples_freq) sigscale = (1. * n_samples) / np.sqrt(nbinsactive) if type == 'perfect_white': ampl[selection] = amplitude * sigscale elif type == 'rayleigh': fsigma = amplitude * sigscale / np.sqrt(2.) ampl[selection] = self.__random_generator.rayleigh( fsigma, nbinsactive) # elif type == 'white': # FIXME: amplitude normalization is not correct for 'white' # ampl = np.random.rand(n_samples) * 0.05 * amplitude + amplitude * np.sqrt(2.*n_samples * 2) else: self.logger.error("Other types of noise not yet implemented.") raise NotImplementedError( "Other types of noise not yet implemented.") noise = self.add_random_phases(ampl, n_samples) / sampling_rate if (time_domain): return fft.freq2time(noise, sampling_rate, n=n_samples) else: return noise
def run(self, event, station, max_distance, z_width, grid_spacing, direction_guess=None, debug=False, use_dnr=False): """ Execute the 2D vertex reconstruction Parameters --------------- station: Station The station for which the vertex shall be reconstructed max_distance: number Maximum distance up to which the vertex position shall be searched z_width: number Vertical size of the search area. If direction_guess is specified, a stripe of z_width to each side of the initial direction will be searched. If direction_guess is not specified, z_width is the maximum depth up to which the vertex will be searched. grid_spacing: number Distance between two points of the grid on which the vertex is searched direction_guess: number, defaults to None Zenith for an initial guess of the vertex direction. If specified, a strip if width 2*z_width around the guessed direction will be searched debug: boolean If True, debug plots will be produced use_dnr: boolean If True, DnR pulses are included in the reconstruction by correlating the channel waveforms with themselves. """ distances = np.arange(50. * units.m, max_distance, grid_spacing) if direction_guess is None: heights = np.arange(-z_width, 0, grid_spacing) else: heights = np.arange(-z_width, z_width, grid_spacing) x_0, z_0 = np.meshgrid(distances, heights) # Create list of coordinates at which we look for the vertex position # If we have an initial guess for the vertex direction, we only check possible vertex locations around that # direction, otherwise we search the whole space if direction_guess is None: x_coords = x_0 z_coords = z_0 else: x_coords = np.cos(direction_guess - 90. * units.deg) * x_0 + np.sin(direction_guess - 90. * units.deg) * z_0 z_coords = -np.sin(direction_guess - 90. * units.deg) * x_0 + np.cos(direction_guess - 90. * units.deg) * z_0 correlation_sum = np.zeros(x_coords.shape) corr_range = 50. * units.ns for i_pair, channel_pair in enumerate(self.__channel_pairs): ch1 = station.get_channel(channel_pair[0]) ch2 = station.get_channel(channel_pair[1]) snr1 = np.max(np.abs(ch1.get_trace())) snr2 = np.max(np.abs(ch2.get_trace())) if snr1 == 0 or snr2 == 0: continue spec1 = np.copy(ch1.get_frequency_spectrum()) spec2 = np.copy(ch2.get_frequency_spectrum()) if self.__passband is not None: b, a = scipy.signal.butter(10, self.__passband, 'bandpass', analog=True) w, h = scipy.signal.freqs(b, a, ch1.get_frequencies()) spec1 *= h spec2 *= h trace1 = fft.freq2time(spec1, ch1.get_sampling_rate()) trace2 = fft.freq2time(spec2, ch2.get_sampling_rate()) if self.__template is not None: corr_1 = hp.get_normalized_xcorr(trace1, self.__template) corr_2 = hp.get_normalized_xcorr(trace2, self.__template) self.__correlation = np.zeros_like(corr_1) sample_shifts = np.arange(-len(corr_1) // 2, len(corr_1) // 2, dtype=int) toffset = sample_shifts / ch1.get_sampling_rate() for i_shift, shift_sample in enumerate(sample_shifts): self.__correlation[i_shift] = np.max(corr_1 * np.roll(corr_2, shift_sample)) else: t_max1 = ch1.get_times()[np.argmax(np.abs(trace1))] t_max2 = ch2.get_times()[np.argmax(np.abs(trace2))] if snr1 > snr2: trace1[np.abs(ch1.get_times() - t_max1) > corr_range] = 0 else: trace2[np.abs(ch2.get_times() - t_max2) > corr_range] = 0 self.__correlation = np.abs(scipy.signal.correlate(trace1, trace2)) toffset = -(np.arange(0, self.__correlation.shape[0]) - self.__correlation.shape[0] / 2.) / ch1.get_sampling_rate() if np.sum(np.abs(self.__correlation)) > 0: self.__correlation /= np.sum(np.abs(self.__correlation)) corr_snr = np.max(self.__correlation) / np.mean(self.__correlation[self.__correlation > 0]) self.__sampling_rate = ch1.get_sampling_rate() self.__channel_pair = channel_pair self.__channel_positions = [self.__detector.get_relative_position(self.__station_id, channel_pair[0]), self.__detector.get_relative_position(self.__station_id, channel_pair[1])] correlation_array = np.zeros_like(correlation_sum) # Check every hypothesis for which ray types the antennas might have detected for i_ray in range(len(self.__ray_types)): self.__current_ray_types = self.__ray_types[i_ray] correlation_array = np.maximum(self.get_correlation_array_2d(x_coords, z_coords), correlation_array) if np.max(correlation_array) > 0: if self.__template is None: correlation_sum += correlation_array / np.max(correlation_array) * corr_snr else: correlation_sum += correlation_array max_corr_index = np.unravel_index(np.argmax(correlation_sum), correlation_sum.shape) max_corr_r = x_coords[max_corr_index[0]][max_corr_index[1]] max_corr_z = z_coords[max_corr_index[0]][max_corr_index[1]] if debug: fig1 = plt.figure(figsize=(12, 4)) fig2 = plt.figure(figsize=(8, 12)) ax1_1 = fig1.add_subplot(1, 3, 1) ax1_2 = fig1.add_subplot(1, 3, 2, sharey=ax1_1) ax1_3 = fig1.add_subplot(1, 3, 3) ax1_1.plot(ch1.get_times(), ch1.get_trace() / units.mV, c='C0', alpha=.3) ax1_2.plot(ch2.get_times(), ch2.get_trace() / units.mV, c='C1', alpha=.3) ax1_1.plot(ch1.get_times()[np.abs(trace1) > 0], trace1[np.abs(trace1) > 0] / units.mV, c='C0', alpha=1) ax1_2.plot(ch2.get_times()[np.abs(trace2) > 0], trace2[np.abs(trace2) > 0] / units.mV, c='C1', alpha=1) ax1_1.plot(ch1.get_times()[:len(self.__template)], self.__template, c='k') ax1_1.set_xlabel('t [ns]') ax1_1.set_ylabel('U [mV]') ax1_1.set_title('Channel {}'.format(self.__channel_pair[0])) ax1_2.set_xlabel('t [ns]') ax1_2.set_ylabel('U [mV]') ax1_2.set_title('Channel {}'.format(self.__channel_pair[1])) ax1_3.plot(toffset, self.__correlation) ax1_3.set_title('$SNR_{corr}$=%.2f' % (corr_snr)) ax1_1.grid() ax1_2.grid() ax1_3.grid() fig1.tight_layout() ax2_1 = fig2.add_subplot(211) ax2_2 = fig2.add_subplot(212) corr_plots = ax2_1.pcolor(x_coords, z_coords, correlation_array) sum_plots = ax2_2.pcolor(x_coords, z_coords, correlation_sum) fig2.colorbar(corr_plots, ax=ax2_1) fig2.colorbar(sum_plots, ax=ax2_2) sim_vertex = None for shower in event.get_sim_showers(): if shower.has_parameter(shp.vertex): sim_vertex = shower.get_parameter(shp.vertex) if sim_vertex is not None: ax2_1.axvline(np.sqrt(sim_vertex[0]**2 + sim_vertex[1]**2), c='r', linestyle=':') ax2_1.axhline(sim_vertex[2], c='r', linestyle=':') ax2_2.axvline(np.sqrt(sim_vertex[0]**2 + sim_vertex[1]**2), c='r', linestyle=':') ax2_2.axhline(sim_vertex[2], c='r', linestyle=':') ax2_1.axvline(max_corr_r, c='k', linestyle=':') ax2_1.axhline(max_corr_z, c='k', linestyle=':') ax2_2.axvline(max_corr_r, c='k', linestyle=':') ax2_2.axhline(max_corr_z, c='k', linestyle=':') fig2.tight_layout() plt.show() plt.close('all') if use_dnr: dnr_correlation_sum = np.zeros(x_coords.shape) for channel_id in self.__channel_ids: channel = station.get_channel(channel_id) spec = channel.get_frequency_spectrum() if self.__passband is not None: b, a = scipy.signal.butter(10, self.__passband, 'bandpass', analog=True) w, h = scipy.signal.freqs(b, a, channel.get_frequencies()) spec *= h trace = fft.freq2time(spec, channel.get_sampling_rate()) corr = hp.get_normalized_xcorr(trace, self.__template) self.__correlation = np.zeros_like(corr) sample_shifts = np.arange(-len(corr) // 2, len(corr) // 2, dtype=int) toffset = sample_shifts / channel.get_sampling_rate() for i_shift, shift_sample in enumerate(sample_shifts): self.__correlation[i_shift] = np.max(corr * np.roll(corr, shift_sample)) self.__correlation[np.abs(toffset) <= 5] = 0 self.__sampling_rate = channel.get_sampling_rate() self.__channel_pair = [channel_id, channel_id] self.__channel_positions = [self.__detector.get_relative_position(self.__station_id, channel_id), self.__detector.get_relative_position(self.__station_id, channel_id)] correlation_array = np.zeros_like(correlation_sum) for i_ray in range(len(self.__dnr_ray_types)): self.__current_ray_types = self.__dnr_ray_types[i_ray] correlation_array = np.maximum(self.get_correlation_array_2d(x_coords, z_coords), correlation_array) if np.max(correlation_array) > 0: dnr_correlation_sum += correlation_array max_corr_dnr_index = np.unravel_index(np.argmax(correlation_sum + dnr_correlation_sum), correlation_sum.shape) max_corr_dnr_r = x_coords[max_corr_dnr_index[0]][max_corr_dnr_index[1]] max_corr_dnr_z = z_coords[max_corr_dnr_index[0]][max_corr_dnr_index[1]] if self.__output_path is not None: plt.close('all') if use_dnr: fig3 = plt.figure(figsize=(12, 12)) ax3_1 = fig3.add_subplot(321) ax3_2 = fig3.add_subplot(322) ax3_3 = fig3.add_subplot(3, 2, (3, 6)) else: fig3 = plt.figure(figsize=(8, 8)) ax3_1 = fig3.add_subplot(111) import skimage.transform downscaled_image = skimage.transform.rescale(correlation_sum, .2) rescaled_xcoords = skimage.transform.rescale(x_coords, .2) rescaled_zcoords = skimage.transform.rescale(z_coords, .2) corr_plot = ax3_1.pcolor(rescaled_xcoords, rescaled_zcoords, downscaled_image) ax3_1.grid() ax3_1.set_aspect('equal') plt.colorbar(corr_plot, ax=ax3_1) sim_vertex = None for shower in event.get_sim_showers(): if shower.has_parameter(shp.vertex): sim_vertex = shower.get_parameter(shp.vertex) if sim_vertex is not None: ax3_1.axvline(np.sqrt(sim_vertex[0] ** 2 + sim_vertex[1] ** 2), c='r', linestyle=':') ax3_1.axhline(sim_vertex[2], c='r', linestyle=':') ax3_1.axvline(max_corr_r, c='k', linestyle=':') ax3_1.axhline(max_corr_z, c='k', linestyle=':') if use_dnr: downscaled_dnr_image = skimage.transform.rescale(dnr_correlation_sum, .2) dnr_corr_plot = ax3_2.pcolor(rescaled_xcoords, rescaled_zcoords, downscaled_dnr_image) if np.max(downscaled_dnr_image) > .1: ax3_2.contour(rescaled_xcoords, rescaled_zcoords, downscaled_dnr_image, levels=[.1], colors='k', alpha=.3) ax3_2.grid() ax3_2.set_aspect('equal') plt.colorbar(dnr_corr_plot, ax=ax3_2) combined_corr_plot = ax3_3.pcolor(rescaled_xcoords, rescaled_zcoords, downscaled_dnr_image + downscaled_image) ax3_3.grid() ax3_3.set_aspect('equal') plt.colorbar(combined_corr_plot, ax=ax3_3) if sim_vertex is not None: ax3_2.axvline(np.sqrt(sim_vertex[0] ** 2 + sim_vertex[1] ** 2), c='r', linestyle=':') ax3_2.axhline(sim_vertex[2], c='r', linestyle=':') ax3_3.axvline(np.sqrt(sim_vertex[0] ** 2 + sim_vertex[1] ** 2), c='r', linestyle=':') ax3_3.axhline(sim_vertex[2], c='r', linestyle=':') ax3_3.axvline(max_corr_dnr_r, c='k', linestyle=':') ax3_3.axhline(max_corr_dnr_z, c='k', linestyle=':') fig3.tight_layout() fig3.savefig('{}/vertex_reco_{}.png'.format(self.__output_path, event.get_id())) if max_corr_index is None: return self.__rec_x = x_coords[max_corr_index[0]][max_corr_index[1]] self.__rec_z = z_coords[max_corr_index[0]][max_corr_index[1]] station.set_parameter(stnp.vertex_2D_fit, [self.__rec_x, self.__rec_z]) return
def run(self, event, station, det, channel_ids, passband): """ Run the module on a station Parameters: _________________ event: NuRadioReco.framework.event.Event object The event the module should be run on station: NuRadioReco.framework.station.Station object The station the module should be run on det: NuRadioReco.detector.detector.Detector object of child object The detector description channel_ids: array of int IDs of the channels the module should be run on passband: array of float Lower and upper bound of the bandpass filter that is applied to the channel waveforms when determining correlation to the template. Can be used to filter out frequencies that are dominated by noise. """ # Create data structured to store pulse properties propagation_times = np.zeros((len(channel_ids), 3)) receive_angles = np.zeros((len(channel_ids), 3)) found_solutions = np.zeros((len(channel_ids), 3)) # Get vertex position vertex_position = None if self.__use_sim: for sim_shower in event.get_sim_showers(): if sim_shower.has_parameter(shp.vertex): vertex_position = sim_shower.get_parameter(shp.vertex) break else: if station.has_parameter(stnp.nu_vertex): vertex_position = station.get_parameter(stnp.nu_vertex) elif station.has_parameter(stnp.vertex_2D_fit): vertex_2d = station.get_parameter(stnp.vertex_2D_fit) vertex_position = np.array([vertex_2d[0], 0, vertex_2d[1]]) if vertex_position is None: raise RuntimeError('Could not find vertex position') correlation_size = 0 for i_channel, channel_id in enumerate(channel_ids): channel = station.get_channel(channel_id) if channel.get_sampling_rate( ) != self.__electric_field_template.get_sampling_rate(): raise RuntimeError( 'The channels and the electric field remplate need to have the same sampling rate.' ) # Calculate size of largest autocorrelation if channel.get_number_of_samples( ) + self.__electric_field_template.get_number_of_samples( ) - 1 > correlation_size: correlation_size = channel.get_number_of_samples( ) + self.__electric_field_template.get_number_of_samples() - 1 channel_position = det.get_relative_position( station.get_id(), channel_id) raytracer = NuRadioMC.SignalProp.analyticraytracing.ray_tracing( x1=vertex_position, x2=channel_position, medium=self.__medium) raytracer.find_solutions() # Loop through all 3 ray path types and store the properties into the data structure for i_solution, solution in enumerate(raytracer.get_results()): solution_type = raytracer.get_solution_type(i_solution) found_solutions[i_channel, solution_type - 1] += 1 propagation_times[i_channel, solution_type - 1] = raytracer.get_travel_time(i_solution) receive_vector = raytracer.get_receive_vector(i_solution) receive_angles[i_channel, solution_type - 1] = radiotools.helper.cartesian_to_spherical( receive_vector[0], receive_vector[1], receive_vector[2])[0] # We only want the relative time differences between channels, so we subtract the mean propagation time for i_solution in range(3): if len(propagation_times[:, i_solution][ propagation_times[:, i_solution] > 0]) > 0: propagation_times[:, i_solution][ propagation_times[:, i_solution] > 0] -= np.mean( propagation_times[:, i_solution][ propagation_times[:, i_solution] > 0]) correlation_sum = np.zeros((3, correlation_size)) # Now we check which ray path results in the best correlation between channels for i_channel, channel_id in enumerate(channel_ids): channel = station.get_channel(channel_id) antenna_pattern = self.__antenna_provider.load_antenna_pattern( det.get_antenna_model(station.get_id(), channel_id)) antenna_orientation = det.get_antenna_orientation( station.get_id(), channel_id) for i_solution in range(3): if found_solutions[i_channel, i_solution] > 0: # We calculate the voltage template from the electric field template using the receiving angles # for that raytracing solution antenna_response = antenna_pattern.get_antenna_response_vectorized( self.__electric_field_template.get_frequencies(), receive_angles[i_channel, i_solution], 0., antenna_orientation[0], antenna_orientation[1], antenna_orientation[2], antenna_orientation[3]) # For simplicity, we assume equal contribution on the E_theta and E_phi component channel_template_spec = fft.time2freq(self.__electric_field_template.get_filtered_trace(passband), self.__electric_field_template.get_sampling_rate()) * \ det.get_amplifier_response(station.get_id(), channel_id, self.__electric_field_template.get_frequencies()) * (antenna_response['theta'] + antenna_response['phi']) channel_template_trace = fft.freq2time( channel_template_spec, self.__electric_field_template.get_sampling_rate()) # We apply the expected time shift for the raytracing solution and calculate the correlation with the template channel.apply_time_shift( -propagation_times[i_channel, i_solution], True) correlation = radiotools.helper.get_normalized_xcorr( channel_template_trace, channel.get_filtered_trace(passband)) correlation = np.abs(correlation) correlation_sum[i_solution][:len(correlation )] += correlation channel.apply_time_shift( propagation_times[i_channel, i_solution], True) correlation_sum_max = np.max(correlation_sum, axis=1) # We look for the raytracing solution that yielded the best correlation and store the corresponding properties # in the channel parameters for i_channel, channel_id in enumerate(channel_ids): channel = station.get_channel(channel_id) channel.set_parameter( chp.signal_time_offset, propagation_times[i_channel, np.argmax(correlation_sum_max)]) channel.set_parameter( chp.signal_receiving_zenith, receive_angles[i_channel, np.argmax(correlation_sum_max)]) channel.set_parameter( chp.signal_ray_type, self.__raytracing_types[np.argmax(correlation_sum_max)])
def update_electric_field_plot( log_energy, viewing_angle, shower_type, polarization_angle, model, propagation_length, attenuation_model, sampling_rate): viewing_angle = viewing_angle * units.deg polarization_angle = polarization_angle * units.deg propagation_length = propagation_length * units.km energy = np.power(10., log_energy) samples = int(512 * sampling_rate) ior = 1.78 cherenkov_angle = np.arccos(1./ior) distance = 1.*units.km try: efield_spectrum = NuRadioMC.SignalGen.askaryan.get_frequency_spectrum( energy, cherenkov_angle + viewing_angle, samples, 1./sampling_rate, shower_type, ior, distance, model, same_shower=True ) except: efield_spectrum = NuRadioMC.SignalGen.askaryan.get_frequency_spectrum( energy, cherenkov_angle + viewing_angle, samples, 1./sampling_rate, shower_type, ior, distance, model, same_shower=False ) freqs = np.fft.rfftfreq(samples, 1./sampling_rate) if propagation_length > 0: attenuation_length = NuRadioMC.utilities.attenuation.get_attenuation_length(200., freqs, attenuation_model) efield_spectrum *= np.exp(-propagation_length/attenuation_length) efield_spectrum_theta = efield_spectrum * np.cos(polarization_angle) efield_spectrum_phi = efield_spectrum * np.sin(polarization_angle) efield_trace = fft.freq2time(efield_spectrum, sampling_rate) efield_trace_theta = efield_trace * np.cos(polarization_angle) efield_trace_phi = efield_trace * np.sin(polarization_angle) times = np.arange(samples) / sampling_rate fig = plotly.subplots.make_subplots(rows=1, cols=2, shared_xaxes=False, shared_yaxes=False, vertical_spacing=0.01, subplot_titles=['Time Trace', 'Spectrum']) fig.append_trace(go.Scatter( x=times/units.ns, y=efield_trace_theta/(units.mV/units.m), name='E_theta (t)' ),1,1) fig.append_trace(go.Scatter( x=times/units.ns, y=efield_trace_phi/(units.mV/units.m), name='E_phi (t)' ),1,1) fig.append_trace(go.Scatter( x=freqs/units.MHz, y=np.abs(efield_spectrum_theta)/(units.mV/units.m/units.GHz), name='E_theta (f)' ),1,2) fig.append_trace(go.Scatter( x=freqs/units.MHz, y=np.abs(efield_spectrum_phi)/(units.mV/units.m/units.GHz), name='E_phi (f)' ),1,2) max_time = times[np.argmax(np.sqrt(efield_trace_phi**2+efield_trace_theta**2))] fig.update_xaxes(title_text='t [ns]', range=[max_time-50*units.ns, max_time+50*units.ns], row=1, col=1) fig.update_xaxes(title_text='f [MHz]', row=1, col=2) fig.update_yaxes(title_text='E[mV/m]', row=1, col=1) fig.update_yaxes(title_text='E [mV/m/GHz]', row=1, col=2) return [fig, json.dumps({'theta':efield_trace_theta.tolist(), 'phi':efield_trace_phi.tolist()})]
def run(self, evt, station, det): t = time.time() # access simulated efield and high level parameters sim_station = station.get_sim_station() if (len(sim_station.get_electric_fields()) == 0): raise LookupError(f"station {station.get_id()} has no efields") sim_station_id = sim_station.get_id() # first we determine the trace start time of all channels and correct # for different cable delays times_min = [] times_max = [] for iCh in det.get_channel_ids(sim_station_id): for electric_field in sim_station.get_electric_fields_for_channels( [iCh]): time_resolution = 1. / electric_field.get_sampling_rate() cab_delay = det.get_cable_delay(sim_station_id, iCh) t0 = electric_field.get_trace_start_time() + cab_delay # if we have a cosmic ray event, the different signal travel time to the antennas has to be taken into account if sim_station.is_cosmic_ray(): site = det.get_site(sim_station_id) antenna_position = det.get_relative_position( sim_station_id, iCh) - electric_field.get_position() if sim_station.get_parameter( stnp.zenith ) > 90 * units.deg: # signal is coming from below, so we take IOR of ice index_of_refraction = ice.get_refractive_index( antenna_position[2], site) else: # signal is coming from above, so we take IOR of air index_of_refraction = ice.get_refractive_index(1, site) # For cosmic ray events, we only have one electric field for all channels, so we have to account # for the difference in signal travel between channels. IMPORTANT: This is only accurate # if all channels have the same z coordinate travel_time_shift = geo_utl.get_time_delay_from_direction( sim_station.get_parameter(stnp.zenith), sim_station.get_parameter(stnp.azimuth), antenna_position, index_of_refraction) t0 += travel_time_shift if ( not np.isnan(t0) ): # trace start time is None if no ray tracing solution was found and channel contains only zeros times_min.append(t0) times_max.append(t0 + electric_field.get_number_of_samples() / electric_field.get_sampling_rate()) self.logger.debug( "trace start time {}, cab_delty {}, tracelength {}". format( electric_field.get_trace_start_time(), cab_delay, electric_field.get_number_of_samples() / electric_field.get_sampling_rate())) times_min = np.array(times_min) times_max = np.array(times_max) if times_min.min() < 0: times_min -= times_min.min() times_max -= times_min.min() times_min = np.array(times_min) - self.__pre_pulse_time times_max = np.array(times_max) + self.__post_pulse_time trace_length = times_max.max() - times_min.min() trace_length_samples = int(round(trace_length / time_resolution)) if trace_length_samples % 2 != 0: trace_length_samples += 1 self.logger.debug( "smallest trace start time {:.1f}, largest trace time {:.1f} -> n_samples = {:d} {:.0f}ns)" .format(times_min.min(), times_max.max(), trace_length_samples, trace_length / units.ns)) # loop over all channels for channel_id in det.get_channel_ids(station.get_id()): # one channel might contain multiple channels to store the signals from multiple ray paths, # so we loop over all simulated channels with the same id, # convolve each trace with the antenna response for the given angles # and everything up in the time domain self.logger.debug('channel id {}'.format(channel_id)) channel = NuRadioReco.framework.channel.Channel(channel_id) channel_spectrum = None trace_object = None if (self.__debug): from matplotlib import pyplot as plt fig, axes = plt.subplots(2, 1) for electric_field in sim_station.get_electric_fields_for_channels( [channel_id]): # all simulated channels have a different trace start time # in a measurement, all channels have the same physical start time # so we need to create one long trace that can hold all the different channel times # to achieve a good time resolution, we upsample the trace first. new_efield = NuRadioReco.framework.base_trace.BaseTrace( ) # create new data structure with new efield length new_efield.set_trace(copy.copy(electric_field.get_trace()), electric_field.get_sampling_rate()) new_trace = np.zeros((3, trace_length_samples)) # calculate the start bin if (not np.isnan(electric_field.get_trace_start_time())): cab_delay = det.get_cable_delay(sim_station_id, channel_id) if sim_station.is_cosmic_ray(): site = det.get_site(sim_station_id) antenna_position = det.get_relative_position( sim_station_id, channel_id) - electric_field.get_position() if sim_station.get_parameter( stnp.zenith ) > 90 * units.deg: # signal is coming from below, so we take IOR of ice index_of_refraction = ice.get_refractive_index( antenna_position[2], site) else: # signal is coming from above, so we take IOR of air index_of_refraction = ice.get_refractive_index( 1, site) travel_time_shift = geo_utl.get_time_delay_from_direction( sim_station.get_parameter(stnp.zenith), sim_station.get_parameter(stnp.azimuth), antenna_position, index_of_refraction) start_time = electric_field.get_trace_start_time( ) + cab_delay - times_min.min() + travel_time_shift start_bin = int(round(start_time / time_resolution)) time_remainder = start_time - start_bin * time_resolution else: start_time = electric_field.get_trace_start_time( ) + cab_delay - times_min.min() start_bin = int(round(start_time / time_resolution)) time_remainder = start_time - start_bin * time_resolution self.logger.debug( 'channel {}, start time {:.1f} = bin {:d}, ray solution {}' .format( channel_id, electric_field.get_trace_start_time() + cab_delay, start_bin, electric_field[efp.ray_path_type])) new_efield.apply_time_shift(time_remainder) new_trace[:, start_bin:(start_bin + new_efield.get_number_of_samples() )] = new_efield.get_trace() trace_object = NuRadioReco.framework.base_trace.BaseTrace() trace_object.set_trace(new_trace, 1. / time_resolution) trace_object.set_trace_start_time( np.min(times_min) - cab_delay) if (self.__debug): axes[0].plot(trace_object.get_times(), new_trace[1], label="eTheta {}".format( electric_field[efp.ray_path_type]), c='C0') axes[0].plot(trace_object.get_times(), new_trace[2], label="ePhi {}".format( electric_field[efp.ray_path_type]), c='C0', linestyle=':') axes[0].plot(electric_field.get_times(), electric_field.get_trace()[1], c='C1', linestyle='-', alpha=.5) axes[0].plot(electric_field.get_times(), electric_field.get_trace()[2], c='C1', linestyle=':', alpha=.5) ff = trace_object.get_frequencies() efield_fft = trace_object.get_frequency_spectrum() zenith = electric_field[efp.zenith] azimuth = electric_field[efp.azimuth] # get antenna pattern for current channel VEL = trace_utilities.get_efield_antenna_factor( sim_station, ff, [channel_id], det, zenith, azimuth, self.antenna_provider) if VEL is None: # this can happen if there is not signal path to the antenna voltage_fft = np.zeros_like( efield_fft[1]) # set voltage trace to zeros else: # Apply antenna response to electric field VEL = VEL[ 0] # we only requested the VEL for one channel, so selecting it voltage_fft = np.sum( VEL * np.array([efield_fft[1], efield_fft[2]]), axis=0) # Remove DC offset voltage_fft[np.where(ff < 5 * units.MHz)] = 0. if (self.__debug): axes[1].plot(trace_object.get_times(), fft.freq2time( voltage_fft, electric_field.get_sampling_rate()), label="{}, zen = {:.0f}deg".format( electric_field[efp.ray_path_type], zenith / units.deg)) if ('amp' in self.__uncertainty): voltage_fft *= np.random.normal( 1, self.__uncertainty['amp'][channel_id]) if ('sys_amp' in self.__uncertainty): voltage_fft *= self.__uncertainty['sys_amp'][channel_id] if (channel_spectrum is None): channel_spectrum = voltage_fft else: channel_spectrum += voltage_fft if (self.__debug): axes[0].legend(loc='upper left') axes[1].legend(loc='upper left') plt.show() if trace_object is None: # this happens if don't have any efield for this channel # set the trace to zeros channel.set_trace(np.zeros(trace_length_samples), 1. / time_resolution) else: channel.set_frequency_spectrum( channel_spectrum, trace_object.get_sampling_rate()) channel.set_trace_start_time(times_min.min()) station.add_channel(channel) self.__t += time.time() - t
def update_voltage_plot( electric_field, antenna_type, signal_zenith, signal_azimuth, amplifier_type, filter_toggle, filter_band, sampling_rate ): samples = int(512 * sampling_rate) electric_field = json.loads(electric_field) if electric_field is None: return {}, {}, "" antenna_pattern = antennapattern_provider.load_antenna_pattern(antenna_type) freqs = np.fft.rfftfreq(samples, 1. / sampling_rate) times = np.arange(samples) / sampling_rate signal_zenith = signal_zenith * units.deg signal_azimuth = signal_azimuth * units.deg antenna_response = antenna_pattern.get_antenna_response_vectorized( freqs, signal_zenith, signal_azimuth, 0., 0., 90.*units.deg, 180.*units.deg ) detector_response_theta = antenna_response['theta'] detector_response_phi = antenna_response['phi'] channel_spectrum = antenna_response['theta'] * fft.time2freq(electric_field['theta'], sampling_rate) + antenna_response['phi'] * fft.time2freq(electric_field['phi'], sampling_rate) if amplifier_type is not None: if amplifier_type == 'iglu' or amplifier_type == 'rno_surface': amp_response = NuRadioReco.detector.RNO_G.analog_components.load_amp_response(amplifier_type) amplifier_response = amp_response['gain'](freqs) * amp_response['phase'](freqs) else: amp_response = NuRadioReco.detector.ARIANNA.analog_components.load_amplifier_response(amplifier_type) amplifier_response = amp_response['gain'](freqs) * amp_response['phase'](freqs) channel_spectrum = channel_spectrum * amplifier_response detector_response_theta *= amplifier_response detector_response_phi *= amplifier_response if 'filter' in filter_toggle: mask = freqs > 0 b, a = scipy.signal.butter(10, filter_band, 'bandpass', analog=True) w, h = scipy.signal.freqs(b, a, freqs[mask]) channel_spectrum[mask] = channel_spectrum[mask] * np.abs(h) detector_response_theta[mask] *= np.abs(h) detector_response_phi[mask] *= np.abs(h) channel_trace = fft.freq2time(channel_spectrum, sampling_rate) max_time = times[np.argmax(np.abs(channel_trace))] fig = plotly.subplots.make_subplots(rows=1, cols=2, shared_xaxes=False, shared_yaxes=False, vertical_spacing=0.01, subplot_titles=['Time Trace', 'Spectrum']) fig.append_trace(go.Scatter( x=times / units.ns, y=channel_trace / units.mV, name='U (t)' ), 1, 1) fig.append_trace(go.Scatter( x=freqs / units.MHz, y=np.abs(channel_spectrum) / (units.mV / units.GHz), name='U (f)' ), 1, 2) fig.update_xaxes(title_text='t [ns]', range=[max_time - 50.*units.ns, max_time + 50.*units.ns], row=1, col=1) fig.update_xaxes(title_text='f [MHz]', row=1, col=2) fig.update_yaxes(title_text='U [mV]', row=1, col=1) fig.update_yaxes(title_text='U [mV/GHz]', row=1, col=2) fig2 = plotly.subplots.make_subplots(rows=1, cols=2, shared_xaxes=False, shared_yaxes=True, vertical_spacing=0.01, subplot_titles=['Theta', 'Phi']) fig2.append_trace(go.Scatter( x=freqs / units.MHz, y=np.abs(detector_response_theta), name='Theta' ), 1, 1) fig2.append_trace(go.Scatter( x=freqs / units.MHz, y=np.abs(detector_response_phi), name='Phi' ), 1, 2) fig2.update_xaxes(title_text='f [MHz]', row=1, col=1) fig2.update_xaxes(title_text='f [MHz]', row=1, col=2) fig2.update_yaxes(title_text='VEL [m]', row=1, col=1) fig2.update_yaxes(title_text='VEL [m]', row=1, col=2) s = io.StringIO() np.savetxt(s, np.array([times, channel_trace]).T, delimiter=',') csv_string = "data:text/csv;charset=utf-8,%EF%BB%BF" + s.getvalue() return [fig, fig2, csv_string]
def run(self, event, station, det, debug=False): azimuth_grid_2d, z_grid_2d = np.meshgrid(self.__azimuths_2d, self.__z_coordinates_2d) distance_correlations = np.zeros(self.__distances_2d.shape) full_correlations = np.zeros( (len(self.__distances_2d), len(self.__z_coordinates_2d), len(self.__azimuths_2d))) if debug: plt.close('all') fig1 = plt.figure(figsize=(12, (len(self.__channel_pairs) // 2 + len(self.__channel_pairs) % 2))) self.__pair_correlations = np.zeros( (len(self.__channel_pairs), station.get_channel( self.__channel_ids[0]).get_number_of_samples() + self.__electric_field_template.get_number_of_samples() - 1)) for i_pair, channel_pair in enumerate(self.__channel_pairs): channel_1 = station.get_channel(channel_pair[0]) channel_2 = station.get_channel(channel_pair[1]) antenna_response = trace_utilities.get_efield_antenna_factor( station=station, frequencies=self.__electric_field_template.get_frequencies(), channels=[channel_pair[0]], detector=det, zenith=90. * units.deg, azimuth=0, antenna_pattern_provider=self.__antenna_pattern_provider)[0] voltage_spec = ( antenna_response[0] * self.__electric_field_template.get_frequency_spectrum() + antenna_response[1] * self.__electric_field_template.get_frequency_spectrum() ) * det.get_amplifier_response( station.get_id(), channel_pair[0], self.__electric_field_template.get_frequencies()) if self.__passband is not None: voltage_spec *= bandpass_filter.get_filter_response( self.__electric_field_template.get_frequencies(), self.__passband, 'butterabs', 10) voltage_template = fft.freq2time(voltage_spec, self.__sampling_rate) voltage_template /= np.max(np.abs(voltage_template)) if self.__passband is None: corr_1 = np.abs( hp.get_normalized_xcorr(channel_1.get_trace(), voltage_template)) corr_2 = np.abs( hp.get_normalized_xcorr(channel_2.get_trace(), voltage_template)) else: corr_1 = np.abs( hp.get_normalized_xcorr( channel_1.get_filtered_trace(self.__passband, 'butterabs', 10), voltage_template)) corr_2 = np.abs( hp.get_normalized_xcorr( channel_2.get_filtered_trace(self.__passband, 'butterabs', 10), voltage_template)) correlation_product = np.zeros_like(corr_1) sample_shifts = np.arange(-len(corr_1) // 2, len(corr_1) // 2, dtype=int) toffset = sample_shifts / channel_1.get_sampling_rate() for i_shift, shift_sample in enumerate(sample_shifts): correlation_product[i_shift] = np.max( (corr_1 * np.roll(corr_2, shift_sample))) self.__pair_correlations[i_pair] = correlation_product if debug: ax1_1 = fig1.add_subplot( len(self.__channel_pairs) // 2 + len(self.__channel_pairs) % 2, 2, i_pair + 1) ax1_1.grid() ax1_1.plot(toffset, correlation_product) if debug: fig1.tight_layout() fig1.savefig('{}/{}_{}_correlation.png'.format( self.__debug_folder, event.get_run_number(), event.get_id())) for i_dist, distance in enumerate(self.__distances_2d): self.__current_distance = distance correlation_sum = np.zeros_like(azimuth_grid_2d) for i_pair, channel_pair in enumerate(self.__channel_pairs): self.__correlation = self.__pair_correlations[i_pair] self.__channel_pair = channel_pair self.__channel_positions = [ self.__detector.get_relative_position( self.__station_id, channel_pair[0]), self.__detector.get_relative_position( self.__station_id, channel_pair[1]) ] correlation_map = np.zeros_like(correlation_sum) for i_ray in range(len(self.__ray_types)): self.__current_ray_types = self.__ray_types[i_ray] correlation_map = np.maximum( self.get_correlation_array_2d(azimuth_grid_2d, z_grid_2d), correlation_map) correlation_sum += correlation_map distance_correlations[i_dist] = np.max(correlation_sum) full_correlations[i_dist] = correlation_sum corr_fit_threshold = .7 * np.max(full_correlations) flattened_corr = np.max(full_correlations, axis=2).T i_max_d = np.argmax(flattened_corr, axis=0) corr_mask_d = np.max(flattened_corr, axis=0) > corr_fit_threshold line_fit_d = np.polyfit(self.__distances_2d[corr_mask_d], self.__z_coordinates_2d[i_max_d][corr_mask_d], 1) residuals_d = np.sum( (self.__z_coordinates_2d[i_max_d][corr_mask_d] - self.__distances_2d[corr_mask_d] * line_fit_d[0] - line_fit_d[1]) **2) / np.sum(corr_mask_d.astype(int)) i_max_z = np.argmax(flattened_corr, axis=1) corr_mask_z = np.max(flattened_corr, axis=1) > corr_fit_threshold line_fit_z = np.polyfit(self.__distances_2d[i_max_z][corr_mask_z], self.__z_coordinates_2d[corr_mask_z], 1) residuals_z = np.sum( (self.__z_coordinates_2d[corr_mask_z] - self.__distances_2d[i_max_z][corr_mask_z] * line_fit_z[0] - line_fit_z[1])**2) / np.sum(corr_mask_z.astype(int)) if residuals_d <= residuals_z: slope = line_fit_d[0] offset = line_fit_d[1] max_z_offset = np.max([ 50, np.min([ 200, np.max(self.__z_coordinates_2d[i_max_d][corr_mask_d] - self.__distances_2d[corr_mask_d] * slope - offset) ]) ]) min_z_offset = np.max([ 50, np.min([ 200, np.max(-self.__z_coordinates_2d[i_max_d][corr_mask_d] + self.__distances_2d[corr_mask_d] * slope + offset) ]) ]) flattened_corr_theta = np.max(full_correlations, axis=1) theta_corr_mask = np.max(flattened_corr_theta, axis=1) >= corr_fit_threshold i_max_theta = np.argmax(flattened_corr_theta, axis=1) median_theta = np.median( self.__azimuths_2d[i_max_theta][theta_corr_mask]) z_fit = False else: slope = line_fit_z[0] offset = line_fit_z[1] max_z_offset = np.max([ 50, np.min([ 200, np.max(self.__z_coordinates_2d[corr_mask_z] - self.__distances_2d[i_max_z][corr_mask_z] * slope - offset) ]) ]) min_z_offset = np.max([ 50, np.min([ 200, np.max(-self.__z_coordinates_2d[corr_mask_z] + self.__distances_2d[i_max_z][corr_mask_z] * slope + offset) ]) ]) flattened_corr_theta = np.max(full_correlations, axis=0) theta_corr_mask = np.max(flattened_corr_theta, axis=1) >= corr_fit_threshold i_max_theta = np.argmax(flattened_corr_theta, axis=1) median_theta = np.median( self.__azimuths_2d[i_max_theta][theta_corr_mask]) z_fit = True if debug: self.__draw_2d_correlation_map(event, full_correlations) self.__draw_search_zones(event, slope, offset, line_fit_d, line_fit_z, min_z_offset, max_z_offset, i_max_d, i_max_z, corr_mask_d, corr_mask_z, z_fit, i_max_theta, theta_corr_mask, median_theta) # <--- 3D Fit ---> # distances_3d = np.arange(self.__distances_2d[0], self.__distances_2d[-1], self.__distance_step_3d) z_coords = slope * distances_3d + offset distances_3d = distances_3d[(z_coords < 0) & (z_coords > -2700)] search_heights = np.arange(-1.1 * min_z_offset, 1.1 * max_z_offset, self.__z_step_3d) x_0, y_0, z_0 = np.meshgrid(distances_3d, self.__widths_3d, search_heights) z_coords = z_0 + slope * x_0 + offset x_coords = np.cos(median_theta) * x_0 - y_0 * np.sin(median_theta) y_coords = np.sin(median_theta) * x_0 + y_0 * np.cos(median_theta) correlation_sum = np.zeros_like(z_coords) for i_pair, channel_pair in enumerate(self.__channel_pairs): self.__correlation = self.__pair_correlations[i_pair] self.__channel_pair = channel_pair self.__channel_positions = [ self.__detector.get_relative_position(self.__station_id, channel_pair[0]), self.__detector.get_relative_position(self.__station_id, channel_pair[1]) ] correlation_map = np.zeros_like(correlation_sum) for i_ray in range(len(self.__ray_types)): self.__current_ray_types = self.__ray_types[i_ray] correlation_map = np.maximum( self.get_correlation_array_3d(x_coords, y_coords, z_coords), correlation_map) correlation_sum += correlation_map i_max = np.unravel_index(np.argmax(correlation_sum), correlation_sum.shape) if debug: self.__draw_vertex_reco(event, correlation_sum / np.max(correlation_sum), x_0, y_0, z_0, x_coords, y_coords, z_coords, slope, offset, median_theta, i_max) # <<--- DnR Reco --->> # self.__self_correlations = np.zeros( (len(self.__channel_ids), station.get_channel( self.__channel_ids[0]).get_number_of_samples() + self.__electric_field_template.get_number_of_samples() - 1)) self_correlation_sum = np.zeros_like(z_coords) for i_channel, channel_id in enumerate(self.__channel_ids): channel = station.get_channel(channel_id) antenna_response = trace_utilities.get_efield_antenna_factor( station=station, frequencies=self.__electric_field_template.get_frequencies(), channels=[channel_id], detector=det, zenith=90. * units.deg, azimuth=0, antenna_pattern_provider=self.__antenna_pattern_provider)[0] voltage_spec = ( antenna_response[0] * self.__electric_field_template.get_frequency_spectrum() + antenna_response[1] * self.__electric_field_template.get_frequency_spectrum() ) * det.get_amplifier_response( station.get_id(), channel_id, self.__electric_field_template.get_frequencies()) if self.__passband is not None: voltage_spec *= bandpass_filter.get_filter_response( self.__electric_field_template.get_frequencies(), self.__passband, 'butter', 10) voltage_template = fft.freq2time(voltage_spec, self.__sampling_rate) voltage_template /= np.max(np.abs(voltage_template)) if self.__passband is None: corr_1 = hp.get_normalized_xcorr(channel.get_trace(), voltage_template) corr_2 = hp.get_normalized_xcorr(channel.get_trace(), voltage_template) else: corr_1 = np.abs( hp.get_normalized_xcorr( channel.get_filtered_trace(self.__passband, 'butter', 10), voltage_template)) corr_2 = np.abs( hp.get_normalized_xcorr( channel.get_filtered_trace(self.__passband, 'butter', 10), voltage_template)) correlation_product = np.zeros_like(corr_1) sample_shifts = np.arange(-len(corr_1) // 2, len(corr_1) // 2, dtype=int) toffset = sample_shifts / channel.get_sampling_rate() for i_shift, shift_sample in enumerate(sample_shifts): correlation_product[i_shift] = np.max( (corr_1 * np.roll(corr_2, shift_sample))) correlation_product = np.abs(correlation_product) correlation_product[np.abs(toffset) < 20] = 0 self.__correlation = correlation_product self.__channel_pair = [channel_id, channel_id] self.__channel_positions = [ self.__detector.get_relative_position(self.__station_id, channel_id), self.__detector.get_relative_position(self.__station_id, channel_id) ] correlation_map = np.zeros_like(correlation_sum) for i_ray in range(len(self.__ray_types)): if self.__ray_types[i_ray][0] != self.__ray_types[i_ray][1]: self.__current_ray_types = self.__ray_types[i_ray] correlation_map = np.maximum( self.get_correlation_array_3d(x_coords, y_coords, z_coords), correlation_map) self_correlation_sum += correlation_map combined_correlations = correlation_sum / len( self.__channel_pairs) + self_correlation_sum / len( self.__channel_ids) i_max_dnr = np.unravel_index(np.argmax(combined_correlations), combined_correlations.shape) vertex_x = x_coords[i_max_dnr] vertex_y = y_coords[i_max_dnr] vertex_z = z_coords[i_max_dnr] station.set_parameter(stnp.nu_vertex, np.array([vertex_x, vertex_y, vertex_z])) if debug: self.__draw_dnr_reco( event, correlation_sum / np.max(correlation_sum), self_correlation_sum / np.max(self_correlation_sum), combined_correlations / np.max(combined_correlations), x_0, y_0, z_0, slope, offset, median_theta, i_max, i_max_dnr)
def run( self, event, station, detector, passband=None ): """ Adds noise resulting from galactic radio emission to the channel traces Parameters -------------- event: Event object The event containing the station to whose channels noise shall be added station: Station object The station whose channels noise shall be added to detector: Detector object The detector description passband: list of float Lower and upper bound of the frequency range in which noise shall be added """ if passband is None: passband = [10 * units.MHz, 1000 * units.MHz] self.__gdsm = pygdsm.pygsm.GlobalSkyModel() site_latitude, site_longitude = detector.get_site_coordinates(station.get_id()) site_location = astropy.coordinates.EarthLocation(lat=site_latitude * astropy.units.deg, lon=site_longitude * astropy.units.deg) station_time = station.get_station_time() station_time.format = 'iso' noise_temperatures = np.zeros((len(self.__interpolaiton_frequencies), healpy.pixelfunc.nside2npix(self.__n_side))) local_cs = astropy.coordinates.AltAz(location=site_location, obstime=station_time) solid_angle = healpy.pixelfunc.nside2pixarea(self.__n_side, degrees=False) pixel_longitudes, pixel_latitudes = healpy.pixelfunc.pix2ang(self.__n_side, range(healpy.pixelfunc.nside2npix(self.__n_side)), lonlat=True) pixel_longitudes *= units.deg pixel_latitudes *= units.deg galactic_coordinates = astropy.coordinates.Galactic(l=pixel_longitudes * astropy.units.rad, b=pixel_latitudes * astropy.units.rad) local_coordinates = galactic_coordinates.transform_to(local_cs) n_ice = ice.get_refractive_index(-0.01, detector.get_site(station.get_id())) # save noise temperatures for all directions and frequencies for i_freq, noise_freq in enumerate(self.__interpolaiton_frequencies): radio_sky = self.__gdsm.generate(noise_freq / units.MHz) radio_sky = healpy.pixelfunc.ud_grade(radio_sky, self.__n_side) noise_temperatures[i_freq] = radio_sky for channel in station.iter_channels(): antenna_pattern = self.__antenna_pattern_provider.load_antenna_pattern(detector.get_antenna_model(station.get_id(), channel.get_id())) freqs = channel.get_frequencies() d_f = freqs[2] - freqs[1] sampling_rate = channel.get_sampling_rate() channel_spectrum = channel.get_frequency_spectrum() passband_filter = (freqs > passband[0]) & (freqs < passband[1]) noise_spec_sum = np.zeros_like(channel.get_frequency_spectrum()) flux_sum = np.zeros(freqs[passband_filter].shape) efield_sum = np.zeros((3, freqs.shape[0]), dtype=np.complex) if self.__debug: plt.close('all') fig = plt.figure(figsize=(12, 8)) ax_1 = fig.add_subplot(221) ax_2 = fig.add_subplot(222) ax_3 = fig.add_subplot(223) ax_4 = fig.add_subplot(224) ax_1.grid() ax_1.set_yscale('log') ax_2.grid() ax_2.set_yscale('log') ax_3.grid() ax_3.set_yscale('log') ax_4.grid() ax_4.set_yscale('log') ax_1.set_xlabel('f [MHz]') ax_2.set_xlabel('f [MHz]') ax_3.set_xlabel('f [MHz]') ax_4.set_xlabel('f [MHz]') ax_1.set_ylabel('T [K]') ax_2.set_ylabel('S [W/m²/MHz]') ax_3.set_ylabel('E [V/m]') ax_4.set_ylabel('U [V]') ax_4.plot(channel.get_frequencies() / units.MHz, np.abs(channel.get_frequency_spectrum()), c='C0') ax_4.set_ylim([1.e-8, None]) fig1 = plt.figure() ax1 = fig1.add_subplot(211) ax2 = fig1.add_subplot(212) ax1.plot(channel.get_times(), channel.get_trace(), label='original trace') ax2.plot(channel.get_frequencies() / units.MHz, np.abs(channel.get_frequency_spectrum()), label='original spectrum') ax1.grid() ax2.grid() for i_pixel in range(healpy.pixelfunc.nside2npix(self.__n_side)): azimuth = local_coordinates[i_pixel].az.rad zenith = np.pi / 2. - local_coordinates[i_pixel].alt.rad if zenith > 90. * units.deg: continue temperature_interpolator = scipy.interpolate.interp1d(self.__interpolaiton_frequencies, np.log10(noise_temperatures[:, i_pixel]), kind='quadratic') noise_temperature = np.power(10, temperature_interpolator(freqs[passband_filter])) # calculate spectral radiance of radio signal using rayleigh-jeans law S = (2. * (scipy.constants.Boltzmann * units.joule / units.kelvin) * freqs[passband_filter]**2 / (scipy.constants.c * units.m / units.s)**2 * (noise_temperature) * solid_angle) S[np.isnan(S)] = 0 # calculate radiance per energy bin S_per_bin = S * d_f flux_sum += S_per_bin # calculate electric field per energy bin from the radiance per bin E = np.sqrt(S_per_bin / (scipy.constants.c * units.m / units.s * scipy.constants.epsilon_0 * (units.coulomb / units.V / units.m))) / (d_f) if self.__debug: ax_1.scatter(self.__interpolaiton_frequencies / units.MHz, noise_temperatures[:, i_pixel] / units.kelvin, c='k', alpha=.01) ax_1.plot(freqs[passband_filter] / units.MHz, noise_temperature, c='k', alpha=.02) ax_2.plot(freqs[passband_filter] / units.MHz, S_per_bin / d_f / (units.watt / units.m**2 / units.MHz), c='k', alpha=.02) ax_3.plot(freqs[passband_filter] / units.MHz, E / (units.V / units.m), c='k', alpha=.02) # assign random phases and polarizations to electric field noise_spectrum = np.zeros((3, freqs.shape[0]), dtype=np.complex) phases = np.random.uniform(0, 2. * np.pi, len(S)) polarizations = np.random.uniform(0, 2. * np.pi, len(S)) noise_spectrum[1][passband_filter] = np.exp(1j * phases) * E * np.cos(polarizations) noise_spectrum[2][passband_filter] = np.exp(1j * phases) * E * np.sin(polarizations) efield_sum += noise_spectrum antenna_orientation = detector.get_antenna_orientation(station.get_id(), channel.get_id()) # consider signal reflection at ice surface if detector.get_relative_position(station.get_id(), channel.get_id())[2] < 0: t_theta = geometryUtilities.get_fresnel_t_p(zenith, n_ice, 1) t_phi = geometryUtilities.get_fresnel_t_s(zenith, n_ice, 1) fresnel_zenith = geometryUtilities.get_fresnel_angle(zenith, 1., n_ice) if fresnel_zenith is None: continue else: t_theta = 1 t_phi = 1 fresnel_zenith = zenith # fold electric field with antenna response antenna_response = antenna_pattern.get_antenna_response_vectorized(freqs, fresnel_zenith, azimuth, *antenna_orientation) channel_noise_spectrum = antenna_response['theta'] * noise_spectrum[1] * t_theta + antenna_response['phi'] * noise_spectrum[2] * t_phi if self.__debug: ax_4.plot(freqs / units.MHz, np.abs(channel_noise_spectrum) / units.V, c='k', alpha=.01) channel_spectrum += channel_noise_spectrum noise_spec_sum += channel_noise_spectrum channel.set_frequency_spectrum(channel_spectrum, sampling_rate) if self.__debug: ax_2.plot(freqs[passband_filter] / units.MHz, flux_sum / d_f / (units.watt / units.m**2 / units.MHz), c='C0', label='total flux') ax_3.plot(freqs[passband_filter] / units.MHz, np.sqrt(np.abs(efield_sum[1])**2 + np.abs(efield_sum[2])**2)[passband_filter] / (units.V / units.m), c='k', linestyle='-', label='sum of E-fields') ax_3.plot(freqs[passband_filter] / units.MHz, np.sqrt(flux_sum / (scipy.constants.c * (units.m / units.s)) / (scipy.constants.epsilon_0 * (units.farad / units.m))) / d_f / (units.V / units.m), c='C2', label='E-field from total flux') ax_4.plot(channel.get_frequencies() / units.MHz, np.abs(noise_spec_sum), c='k', linestyle=':', label='total noise') ax_2.legend() ax_3.legend() ax_4.legend() fig.tight_layout() ax1.plot(channel.get_times(), channel.get_trace(), label='new trace') ax1.plot(channel.get_times(), fft.freq2time(noise_spec_sum, channel.get_sampling_rate()), label='noise') ax2.plot(channel.get_frequencies() / units.MHz, np.abs(channel.get_frequency_spectrum()), label='new spectrum') ax2.plot(channel.get_frequencies() / units.MHz, np.abs(noise_spec_sum), label='noise') ax1.legend() ax2.legend() plt.show()
ax.legend() ax.set_title("askaryan module") plt.show() a = 1 / 0 # 2nd pyrex fig, ax = plt.subplots(1, 1) n_trace = 2**10 # samples of trace dt = 0.1 * units.ns tt = np.arange(0, n_trace * dt, dt) ff = np.fft.rfftfreq(n_trace, dt) trace = fft.freq2time( param.get_frequency_spectrum(E, theta, ff, 0, n_index, R, model='Alvarez2012'), 1 / dt) ax.plot(tt / units.ns, trace, label="dt = {:.2f}ns, n={}".format(dt / units.ns, n_trace)) n_trace = 2**12 # samples of trace dt = 0.1 * units.ns tt = np.arange(0, n_trace * dt, dt) ff = np.fft.rfftfreq(n_trace, dt) trace = fft.freq2time( param.get_frequency_spectrum(E, theta, ff,