import numpy as np
import logging
from scipy.interpolate import interp1d
import os
from NuRadioReco.utilities import units
from functools import lru_cache
from radiotools import helper as hp
logger = logging.getLogger('NuRadioReco.analog_components')
[docs]
@lru_cache(maxsize=128)
def load_amp_response(amp_type='rno_surface', temp=293.15,
path=os.path.dirname(os.path.realpath(__file__))): # use this function to read in log data
"""
Read out amplifier gain and phase.
These are all 'placeholder' measurements of a single reference amplifier/signal chain;
more up-to-date, channel-specific amplifier responses may be loaded via
the :class:`rnog_detector <NuRadioReco.detector.RNO_G.rnog_detector.Detector>` class.
Temperature dependence: the function loads a reference measurement made at room temperature
and will correct it for the temperature. The correction function is obtained empirically for
one amplifier of reference (one Surface board and one DRAB + fiber + IGLU chain)
by studying its gain in a climate chamber at different temperatures.
Parameters
----------
amp_type: string
* "rno_surface": the surface signal chain
* "iglu": the in-ice signal chain
* "phased_array": the additional filter of the phased array channels before going into the phased array trigger.
* "rno_surface_impulse": alternative measurement of the surface signal chain response, including the RADIANT
* "deep_impulse": alternative measurement of the deep (iglu) signal chain response, including the RADIANT
temp: float (default 293.15K)
the default temperature in Kelvin that the amplifier response is corrected for
"""
# definition correction functions: temp in Kelvin, freq in GHz
# functions defined in temperature range [223.15 K , 323.15 K]
def surface_correction_func(temp, freqs):
return 1.0377798029 - 0.00135258197 * (temp - 273.15) + (0.4788208019 - 0.01790064797 * (temp - 273.15)) * (freqs ** 5)
def iglu_correction_func(temp, freqs):
return 1.1139014286 - 0.00004392995 * ((temp - 273.15) + 28.8331610295) ** 2 + (0.6301058083 - 0.0208741539 * (temp - 273.15)) * (freqs ** 5)
amp_response = {}
correction_function = None
if amp_type == 'rno_surface':
ph = os.path.join(path, 'HardwareResponses/surface_placeholder.csv')
ff = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=0)
ff *= units.Hz
amp_gain_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=1)
amp_phase_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=2)
correction_function = surface_correction_func
elif amp_type == 'rno_surface_impulse':
ph = os.path.join(path, 'HardwareResponses/surface_impulse_response_placeholder.csv')
ff = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=0)
ff *= units.Hz
amp_gain_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=1)
amp_phase_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=2)
elif amp_type == 'iglu':
ph = os.path.join(path, 'HardwareResponses/iglu_drab_placeholder.csv')
ff = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=0)
ff *= units.Hz
amp_gain_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=1)
amp_phase_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=2)
correction_function = iglu_correction_func
elif amp_type == 'deep_impulse':
ph = os.path.join(path, 'HardwareResponses/deep_impulse_response_placeholder.csv')
ff = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=0)
ff *= units.Hz
amp_gain_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=1)
amp_phase_discrete = np.loadtxt(ph, delimiter=',', skiprows=1, usecols=2)
elif amp_type == 'phased_array' or amp_type == 'ULP_216':
ph = os.path.join(path, 'HardwareResponses/ULP-216+_Plus25DegC.s2p')
ff, S11gain, S11deg, S21gain, S21deg, S12gain, S12deg, S22gain, S22deg = np.loadtxt(ph, comments=['#', '!'], unpack=True)
ff *= units.MHz
# Convert gain in dB power to linear in voltage
amp_gain_discrete = 10**(S21gain / 20)
amp_phase_discrete = S21deg * units.deg
else:
msg = f"Amp type `{amp_type}` not recognized. possible values are {get_available_amplifiers()}"
logger.error(msg)
raise ValueError(msg)
amp_gain_f = interp1d(ff, amp_gain_discrete, bounds_error=False, fill_value=0)
# all requests outside of measurement range are set to 1
def get_amp_gain(freqs, temp=temp):
if correction_function is not None:
amp_gain = correction_function(temp, freqs) * amp_gain_f(freqs)
else:
amp_gain = amp_gain_f(freqs)
return amp_gain
# Convert to MHz and broaden range (all requests outside of measurement range are set to 0)
amp_phase_f = interp1d(ff, np.unwrap(amp_phase_discrete),
bounds_error=False, fill_value=0)
def get_amp_phase(freqs):
amp_phase = amp_phase_f(freqs)
return np.exp(1j * amp_phase)
amp_response['gain'] = get_amp_gain
amp_response['phase'] = get_amp_phase
return amp_response
[docs]
def get_available_amplifiers():
"""Returns a list of available amplifiers"""
return ['iglu', 'deep_impulse', 'rno_surface', 'rno_surface_impulse', 'phased_array', 'ULP_216']