Source code for dysh.spectra.vane

"""
VaneSpectrum
"""

import numpy as np
from astropy import units as u
from astropy.time import Time

from dysh.log import logger

from ..util.docstring_manip import docstring_parameter
from ..util.gaincorrection import GBTGainCorrection
from ..util.weatherforecast import GBTWeatherForecast
from .core import mean_data
from .spectrum import Spectrum

tcal_param_docs = """tcal : None or float
    The calibration temperature in K.
    If `None`, it will be estimated using :meth:`get_tcal`.
"""

twarm_param_docs = """twarm : None or float
    The vane temperature in K.
    If `None`, it will be inferred from the TWARM column.
"""

zenith_opacity_param_docs = """zenith_opacity : float or None
    The zenith opacity in nepers.
    If `None`, it will be retrieved from the GBO weather forecast scripts (only available at GBO).
    If `None` and not at GBO, `tcal` will equal `twarm` (accurate to approximately 10%).
"""

tatm_param_docs = """tatm : float or None
    The atmospheric temperature towards the zenith in K.
    If `None`, it will be retrieved from the GBO weather forecast scripts (only available at GBO).
    If `None` and not at GBO, `tcal` will equal `twarm` (accurate to approximately 10%).
"""

tbkg_param_docs = """tbkg : float or None
    The background temperature in K.
    Default is the CMB temperature at 3 mm (2.725 K).
"""

param_docs = {
    "tcal": tcal_param_docs,
    "twarm": twarm_param_docs,
    "zenith_opacity": zenith_opacity_param_docs,
    "tatm": tatm_param_docs,
    "tbkg": tbkg_param_docs,
}

vane_kwargs = "".join(param_docs.values())


[docs] @docstring_parameter(vane_kwargs) class VaneSpectrum(Spectrum): r""" Vane calibration spectrum. Parameters ---------- vane : `~numpy.ndarray` Vane power values. The values will define the `data` attribute of the `~dysh.spectra.vane.VaneSpectrum` object. scan : int Scan number. fdnum : int The feed number. ifnum : int The intermediate frequency (IF) number. plnum : int The polarization number. {0} """ def __init__( self, vane, scan, fdnum, ifnum, plnum, tcal=None, twarm=None, zenith_opacity=None, tatm=None, tbkg=None, *args, **kwargs, ): Spectrum.__init__(self, *args, **kwargs) self._vane = vane self._scan = scan self._fdnum = fdnum self._ifnum = ifnum self._plnum = plnum self._tcal = tcal self._twarm = twarm self._zenith_opacity = zenith_opacity self._tatm = tatm self._gbtgc = GBTGainCorrection() try: self._gbtwf = GBTWeatherForecast() except ValueError: logger.info("Weather forecast not available.") self._gbtwf = None self._validate_inputs() def _validate_inputs(self): """ Check that the minimum information is available to compute the equivalent temperature of the vane. """ if self._gbtwf is None: if self._tcal is None: if self.twarm is None: raise ValueError("twarm not known. Set with twarm argument.") @property def scan(self): """The scan number.""" return self._scan @property def ifnum(self): """The intermediate frequency (IF) number.""" return self._ifnum @property def fdnum(self): """The feed number.""" return self._fdnum @property def plnum(self): """The polarization number.""" return self._plnum
[docs] @classmethod @docstring_parameter(vane_kwargs) def from_spectrum( cls, spectrum, scan, fdnum, ifnum, plnum, vane=None, tcal=None, twarm=None, zenith_opacity=None, tatm=None, tbkg=None, ): """ Returns a `~dysh.spectra.vane.VaneSpectrum` object given a `~dysh.spectra.spectrum.Spectrum` object. Parameters ---------- spectrum : `~dysh.spectra.spectrum.Spectrum` Spectrum object. Its attributes will be copied into the `~dysh.spectra.vane.VaneSpectrum` object, except for the `data` and `flux` attributes which can be defined by the `vane` parameter. scan : int Scan number. fdnum : int The feed number. ifnum : int The intermediate frequency (IF) number. plnum : int The polarization number. vane : None or `~numpy.ndarray` Vane power values. If None, then it will use the `flux` attribute of `spectrum`. If set, the values will define the `data` attribute of the `~dysh.spectra.vane.VaneSpectrum` object. {0} Returns ------- `~dysh.spectra.vane.VaneSpectrum` A `~dysh.spectra.vane.VaneSpectrum` object. """ if vane is None: vane = spectrum.flux return cls( vane, scan, ifnum, plnum, fdnum, twarm=twarm, zenith_opacity=zenith_opacity, tatm=tatm, tbkg=tbkg, tcal=tcal, flux=vane, wcs=spectrum.wcs, meta=spectrum.meta, velocity_convention=spectrum.velocity_convention, radial_velocity=spectrum.radial_velocity, rest_value=spectrum.rest_value, observer=spectrum.observer, target=spectrum.target, mask=spectrum.mask, )
@property def twarm(self): """ Vane temperature in K. """ # If twarm has not been explicitly set, then use the value in the header. if self._twarm is not None: return self._twarm else: return self.meta["TWARM"] + 273.15
[docs] @docstring_parameter( "".join(f"{v}" for k, v in param_docs.items() if k in ["twarm", "tatm", "tbkg", "zenith_opacity"]) ) def get_tcal( self, ref, mjd: float | None = None, freq: u.Quantity | None = None, elev: u.Quantity | None = None, zenith_opacity: float | None = None, tatm: float | None = None, twarm: float | None = None, tbkg: float = 2.725, ): r""" Calibration temperature. Parameters ---------- ref : `~dysh.spectra.spectrum.Spectrum` Reference spectrum used to derive :math:`T_{{\mathrm{{cal}}}}`. mjd : float or None Modified Julian date. If None, will use DATE-OBS in `ref.meta`. freq : `~astropy.units.Quantity` or None Frequency at which to compute the calibration temperature. If None, will use OBSFREQ in `ref.meta`. elev : `~astropy.units.Quantity` or None Elevation at which to evaluate the airmass. If None, will use ELEVATIO in `ref.meta`. {0} Returns ------- tcal : float Calibration temperature for the vane in K. """ # If the user set this value upstream. Use it. if self._tcal is not None: return self._tcal if mjd is None: mjd = Time(ref.meta["DATE-OBS"]).mjd if freq is None: freq = ref.meta["OBSFREQ"] * u.Hz if elev is None: elev = ref.meta["ELEVATIO"] * u.deg return self._get_tcal(freq, mjd, elev, zenith_opacity, tatm, twarm, tbkg)
def _get_tcal( self, freq: u.Quantity, mjd: float, elevation: u.Quantity, zenith_opacity: float | None = None, tatm: float | None = None, twarm: float | None = None, tbkg: float = 2.725, ): # If the user set this value upstream. Use it. if self._tcal is not None: return self._tcal if twarm is None: twarm = self.twarm logger.info(f"Vane temperature (twarm): {twarm:.2f} K") if zenith_opacity is None: zenith_opacity = self._zenith_opacity if zenith_opacity is None: if self._gbtwf is not None: result = self._gbtwf.fetch( vartype="Opacity", specval=freq, mjd=mjd, ) zenith_opacity = result[:, -1] logger.info(f"Mean zenith opacity: {np.mean(zenith_opacity):.2f} nepers") else: logger.debug("Could not get forecasted zenith opacity ") logger.debug(f"Zenith opacity: {zenith_opacity} nepers") if tatm is None: tatm = self._tatm if tatm is None: if self._gbtwf is not None: result = self._gbtwf.fetch(vartype="Tatm", specval=freq, mjd=mjd) tatm = result[:, -1] logger.info(f"Mean atmospheric temperature: {np.mean(tatm):.2f} K") else: logger.debug("Could not get forecasted atmospheric temperature ") logger.debug(f"Atmospheric temperature: {tatm} K") if tatm is not None and zenith_opacity is not None: airmass = self._gbtgc.airmass(elevation, zd=False) tcal = (tatm - tbkg) + (twarm - tatm) * np.exp(zenith_opacity * airmass) else: tcal = twarm missing = [] if zenith_opacity is None: missing.extend(["zenith opacity"]) if tatm is None: missing.extend(["atmospheric temperature"]) missing = " nor ".join(missing) logger.info( f"No {missing} available. Will approximate the calibration temperature to the vane temperature {tcal:.2f} K" ) logger.info(f"Mean calibration temperature (tcal): {np.mean(tcal):.2f} K") logger.debug(f"Calibration temperature (tcal): {tcal} K") return tcal def _get_tsys(self, ref, tcal): """ """ mean_off = mean_data(ref) mean_dif = mean_data(self.data - ref) tsys = tcal * mean_off / mean_dif return tsys
[docs] @docstring_parameter(param_docs["tcal"]) def get_tsys(self, ref, tcal=None): """ Compute the system temperature. Parameters ---------- ref : `~dysh.spectra.spectrum.Spectrum` The reference spectrum. {0} Returns ------- tsys : float The system temperature in K. """ if tcal is None: tcal = self.get_tcal(ref) return self._get_tsys(ref.data, tcal)