Source code for dran.calibration.pointing

# =========================================================================== #
# File: pointing.py                                                           #
# Author: Pfesesani V. van Zyl                                                #
# Email: pfesi24@gmail.com                                                    #
# =|========================================================================= #


# Library imports
# ---------------------------------------------------------------------------- #
from dataclasses import dataclass
from typing import Optional, Dict
import numpy as np
import logging
# =|========================================================================= #


[docs] @dataclass(frozen=True, slots=True) class PointingGainResult: pc: Optional[float] ta_corr: Optional[float] ta_corr_err: Optional[float] reason: str
def _safe_log_abs(x: float) -> Optional[float]: """Compute the natural logarithm of the absolute value safely. Returns None for null, non-finite, or non-positive inputs. """ if x is None: return None x = float(x) if not np.isfinite(x): return None ax = abs(x) if ax <= 0.0: return None return float(np.log(ax))
[docs] def calc_pointing_correction(hps_ta, hps_err, hpn_ta, hpn_err, on_ta,on_err, data, missing_side): """Compute pointing correction factor and corrected antenna temperature with propagated uncertainty. Uses safe log transforms of scan peak temperatures to derive a pointing correction based on which beam side is missing, then applies the correction to on-source temperature and propagates errors via partial derivatives. """ # Derivatives of pc w.r.t Ta1 and Ta2 # pc = exp( (d^2)/denom ), d = ln|Ta1| - ln|Ta2| (+/- ln2) # d(pc)/d(Ta1) = pc * (2*d/denom) * (1/Ta1) # d(pc)/d(Ta2) = pc * (2*d/denom) * (-1/Ta2) lnS = _safe_log_abs(hps_ta) lnN = _safe_log_abs(hpn_ta) lnO = _safe_log_abs(on_ta) # print(lnS,lnN,lnO) if missing_side == "n": d = (lnS-lnO) + float(np.log(2.0)) denom = 4.0 * float(np.log(2.0)) expo = (d**2) / denom pc = float(np.exp(expo)) # calculate the derivatives hps_der = pc * 2.0 * expo * (1.0/hps_ta) on_der = pc * 2.0 * expo * (-1.0/on_ta) err_ta_corrected = np.sqrt((hps_err**2)*(hps_der**2) + (on_err**2)*(on_der**2)) elif missing_side == "s": d = (lnO-lnN) - float(np.log(2.0)) denom = 4.0 * float(np.log(2.0)) expo = (d**2) / denom pc = float(np.exp(expo)) # calculate the derivatives on_der = pc * 2.0 * expo * (1.0/on_ta) hpn_der = pc * 2.0 * expo * (-1.0/hpn_ta) try: err_ta_corrected = np.sqrt((on_err**2)*(on_der**2) + (hpn_err**2)*(hpn_der**2)) except: print('Errors are too high, setting errors to 99') print('Err ON: ', on_der,', Err HPN: ', hpn_der) err_ta_corrected = 99 else: d=lnS-lnN denom = 16.0 * float(np.log(2.0)) expo = (d**2) / denom pc = float(np.exp(expo)) # calculate the derivatives hps_der = pc * 2.0 * expo * (1.0/hps_ta) hpn_der = pc * 2.0 * expo * (-1.0/hpn_ta) err_ta_corrected = np.sqrt((hps_err**2)*(hps_der**2) + (hpn_err**2)*(hpn_der**2)) ta_corrected = calc_tcorr(on_ta, pc, data) return pc, ta_corrected, err_ta_corrected
[docs] def calc_tcorr(Ta, pc, data): """ Calculate the antenna temperature correction for high frequencies. Args: Ta - the on scan antenna temperature pc - the pointing correction data - the dictionary containing all the drift scan parameters Returns: corrTa - the corrected antenna temperature """ Ta= float(Ta) pc=float(pc) if data["FRONTEND"] == "01.3S" or data["EXTNAME"] == "01.3S": if data["OBJECT"].upper() == "JUPITER": # Only applying a size correction factor and atmospheric correction to Jupiter # See van Zyl et al. (2026) - in Prep abs=float(data["ATMOS_ABSORPTION_CORR"]) scf=float( data["SIZE_CORRECTION_FACTOR"]) corrTa = Ta * pc * abs * scf return corrTa else: # do we also apply a atmospheric correcttion factor directly to the target source ? # find out, for now I'm not applyin it # tests this ASAP corrTa = Ta * pc return corrTa else: corrTa = Ta*pc return corrTa
[docs] def pointing_gain_from_halfpower( hpn_ta: float, hpn_err: float, on_ta: float, on_err: float, hps_ta: float, hps_err: float, data:Dict, log: logging.Logger, missing_side: Optional[str], ) -> PointingGainResult: """ Compute multiplicative pointing gain factor pc and 1-sigma uncertainty pc_err. missing_side: None -> both half-power points available (south and north) "n" -> missing north half-power, use (HPS, ON) form in caller if needed "s" -> missing south half-power, use (ON, HPN) form in caller if needed This function expects the two temperatures supplied belong to the two half-power points used in the same equation. """ hps_ta = float(abs(hps_ta)) if np.isfinite(hps_ta) else float("nan") hps_err = float(abs(hps_err)) if np.isfinite(hps_err) else float("nan") hpn_ta = float(abs(hpn_ta)) if np.isfinite(hpn_ta) else float("nan") hpn_err = float(abs(hpn_err)) if np.isfinite(hpn_err) else float("nan") on_ta = float(abs(on_ta)) if np.isfinite(on_ta) else float("nan") on_err = float(abs(on_err)) if np.isfinite(on_err) else float("nan") pc, ta, ta_corr = calc_pointing_correction(hps_ta, hps_err, hpn_ta, hpn_err, on_ta,on_err, data, missing_side) return PointingGainResult(pc=pc, ta_corr=ta, ta_corr_err=ta_corr, reason='ok')