Source code for pycraf.protection.ra769

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import (
    absolute_import, unicode_literals, division, print_function
    )

import os
import numpy as np
from astropy import units as apu
# from astropy.units import Quantity, UnitsError
# import astropy.constants as con
from astropy.table import QTable, Table
from astropy.utils.data import get_pkg_data_filename
from .. import conversions as cnv
from .. import utils


__all__ = ['ra769_calculate_entry', 'ra769_limits']


[docs]@utils.ranged_quantity_input( frequency=(1e-9, 1.e12, apu.Hz), bandwidth=(1e-9, 1.e11, apu.Hz), T_A=(1e-9, 100000, apu.K), T_rx=(1e-9, 100000, apu.K), integ_time=(1e-9, None, apu.s) ) def ra769_calculate_entry( frequency, bandwidth, T_A, T_rx, mode='continuum', scale='dB', integ_time=2000. * apu.s, ): ''' Limits (single entry) for spectral line, continuum, and VLBI observations according to `ITU-R Rec RA.769 <https://www.itu.int/rec/R-REC-RA.769-2-200305-I/en>`_. Parameters ---------- frequency : `~astropy.units.Quantity` Center frequency [Hz] bandwidth : `~astropy.units.Quantity` Assumed bandwidth [Hz] Note, if `mode='vlbi'` the bandwidth is irrelevant, since only the spectral power flux density is calculated. T_A : `~astropy.units.Quantity` Minimum antenna noise temperature [K] T_rx : `~astropy.units.Quantity` Receiver noise temperature [K] mode : str, optional Observing mode: 'continuum', 'spectroscopy', or 'vlbi' (default: 'continuum') scale : str, optional Default scale to use: 'linear', 'dB' (default: 'linear') integ_time : `~astropy.units.Quantity`, optional Integration time [s] (default: 2000) Note, if `mode='vlbi'` integration time is irrelevant, because the limits are based on 1% of the receiver noise plus antenna temperature. Returns ------- T_rms : `~astropy.units.Quantity` System noise after integration [K] P_rms_nu : `~astropy.units.Quantity` System noise power spectral density [W/Hz, dB(W/Hz)] Plim : `~astropy.units.Quantity` Power limit [W, dB(W)] Plim_nu : `~astropy.units.Quantity` Spectral power limit [W/Hz, dB(W/Hz)] Slim : `~astropy.units.Quantity` Power flux density (pfd) limit [W/m^2, dB(W/m^2)] Slim_nu : `~astropy.units.Quantity` Spectral power flux density limit [Jy, dB(W/m^2/Hz)] Efield : `~astropy.units.Quantity` Electrical field strength limit [uV/m, db(uV^2/m^2)] Efield_norm : `~astropy.units.Quantity` As `Efield` but normalized to 1 MHz bandwidth [uV/m, db(uV^2/m^2)] Note, if `mode='vlbi'` only `Slim_nu` is returned. Notes ----- Because all columns are Astropy Quantities (see `~astropy.units.Quantity`), one can easily convert between linear and log-scale at a later stage. the `scale` parameter just defines the scale to use initially. ''' modes = ['continuum', 'spectroscopy', 'vlbi'] scales = ['linear', 'dB'] assert mode in modes, ( 'mode must be either "continuum", "spectroscopy", or "vlbi"' ) assert scale in scales, 'scale must be either "linear" or "dB"' if mode != 'vlbi': T_rms = ( (T_A + T_rx) / np.sqrt(integ_time * bandwidth) ).to(apu.mK) _P_rms_nu = cnv.KB * T_rms _Plim = 10 * apu.percent * _P_rms_nu * bandwidth _Slim = 4. * np.pi / cnv.C ** 2 * frequency ** 2 * _Plim P_rms_nu = _P_rms_nu.to( apu.Watt / apu.Hz if scale == 'linear' else cnv.dB_W_Hz ) Plim = _Plim.to(apu.Watt if scale == 'linear' else cnv.dB_W) Plim_nu = (_Plim / bandwidth).to( apu.W / apu.Hz if scale == 'linear' else cnv.dB_W_Hz ) Slim = _Slim.to( (apu.Watt / apu.m ** 2) if scale == 'linear' else cnv.dB_W_m2 ) Slim_nu = (_Slim / bandwidth).to( apu.Jy if scale == 'linear' else cnv.dB_W_m2_Hz ) # field strength from full bandwidth: Efield = (np.sqrt(cnv.R0 * _Slim)).to( (apu.microvolt / apu.m) if scale == 'linear' else cnv.dB_uV_m ) # Efield2 = (Efield ** 2).to(dB_uV_m) # now normalize for 1 MHz BW for comparison with spectroscopy # TODO: is this correct??? Efield_norm = np.sqrt( cnv.R0 * _Slim * apu.MHz / bandwidth.to(apu.MHz) ).to( (apu.microvolt / apu.m) if scale == 'linear' else cnv.dB_uV_m ) # Efield2_norm = (Efield_norm ** 2).to(dB_uV_m) return ( T_rms, P_rms_nu, Plim, Plim_nu, Slim, Slim_nu, Efield, Efield_norm ) else: # The tolerable interference level is determined by the requirement # that the power level of the interfering signal should be no more # than 1% of the receiver noise power to prevent serious errors in # the measurement of the amplitude of the cosmic signals. _Plim_nu = 1 * apu.percent * cnv.KB * (T_rx + T_A) _Slim_nu = 4. * np.pi / cnv.C ** 2 * frequency ** 2 * _Plim_nu Slim_nu = _Slim_nu.to( apu.Jy if scale == 'linear' else cnv.dB_W_m2_Hz ) return Slim_nu
[docs]@utils.ranged_quantity_input(integ_time=(1e-9, None, apu.s)) def ra769_limits(mode='continuum', scale='dB', integ_time=2000. * apu.s): ''' Limits for spectral line, continuum, and VLBI observations according to `ITU-R Rec RA.769 <https://www.itu.int/rec/R-REC-RA.769-2-200305-I/en>`_. Parameters ---------- mode : str, optional Observing mode: 'continuum', 'spectroscopy', or 'vlbi' (default: 'continuum') scale : str, optional Default scale to use: 'linear', 'dB' (default: 'linear') integ_time : `~astropy.units.Quantity`, optional Integration time [s] (default: 2000) Note, if `mode='vlbi'` integration time is irrelevant, because the limits are based on 1% of the receiver noise plus antenna temperature. Returns ------- ra769_limits : `~astropy.table.Table` A table with the following columns: - "frequency" [MHz] - "bandwidth" [MHz] - "T_A" [K] - "T_rx" [K] - "T_rms" [K] - "P_rms_nu" [W/Hz, dB(W/Hz)] - "Plim" [W, dB(W)] - "Plim_nu" [W/Hz, dB(W/Hz)] - "Slim" [W/m^2, dB(W/m^2)] - "Slim_nu" [Jy, dB(W/m^2/Hz)] - "Efield" [uV/m, db(uV^2/m^2)] - "Efield_norm", normalized to 1 MHz bandwidth [uV/m, db(uV^2/m^2)] If `mode='vlbi'` the returned table has the following entries, only - "frequency" [MHz] - "bandwidth" [MHz] - "T_A" [K] - "T_rx" [K] - "Slim_nu" [Jy, dB(W/m^2/Hz)] Notes ----- Because all columns are Astropy Quantities (see `~astropy.units.Quantity`), one can easily convert between linear and log-scale at a later stage. the `scale` parameter just defines the scale to use initially. ''' modes = ['continuum', 'spectroscopy', 'vlbi'] scales = ['linear', 'dB'] assert mode in modes, ( 'mode must be either "continuum", "spectroscopy", or "vlbi"' ) assert scale in scales, 'scale must be either "linear" or "dB"' csv_mode = mode if mode != 'vlbi' else 'continuum' midx = modes.index(csv_mode) csv_name = 'ra_769_table{}_limits_{}.csv'.format(midx + 1, csv_mode) csv_path = get_pkg_data_filename('../itudata/ra.769-2/' + csv_name) csv_tab = np.genfromtxt( csv_path, delimiter=',', skip_header=1, names=True, dtype=np.float64 ) qtab = QTable(meta={'name': 'RA.769 {} limits'.format(mode)}) freq = csv_tab['freq0'] * apu.MHz bandwidth = csv_tab['Delta_f'] * ( apu.kHz if mode == 'spectroscopy' else apu.MHz ) qtab['frequency'] = freq if mode != 'vlbi': qtab['bandwidth'] = bandwidth qtab['T_A'] = csv_tab['T_A'] * apu.K qtab['T_rx'] = csv_tab['T_rx'] * apu.K if mode != 'vlbi': ( qtab['T_rms'], qtab['P_rms_nu'], qtab['Plim'], qtab['Plim_nu'], qtab['Slim'], qtab['Slim_nu'], qtab['Efield'], qtab['Efield_norm'], ) = ra769_calculate_entry( freq, bandwidth, qtab['T_A'], qtab['T_rx'], mode=mode, scale=scale, integ_time=integ_time, ) else: qtab['Slim_nu'] = ra769_calculate_entry( freq, 1 * apu.Hz, qtab['T_A'], qtab['T_rx'], mode=mode, scale=scale, integ_time=integ_time, ) # table formatting doesn't seem to work for QTable, so we temporarily # convert to Table; interestingly, if we cast back to QTable afterwards # the format specifier is still valid (gets converted to QuantityInfo...) tab = Table(qtab) for col in ['frequency', 'bandwidth', 'T_A', 'T_rx']: try: tab[col].format = '%.0f' except KeyError: pass for col in ['T_rms']: try: tab[col].format = '%.3f' except KeyError: pass for col in [ 'P_rms_nu', 'Plim', 'Plim_nu', 'Slim', 'Slim_nu', 'Efield', 'Efield_norm' ]: try: tab[col].format = '%.1e' if scale == 'linear' else '%.1f' except KeyError: pass return QTable(tab)