Source code for ska_ost_senscalc.low.sefd_lookup

import sqlite3

import numpy as np
from scipy.interpolate import UnivariateSpline

from ska_ost_senscalc.utilities import STATIC_DATA_PATH


[docs] class SEFDTable: """ Encapsulation of the SEFD lookup table to be used for SKA LOW calculations """ def __init__(self, db_path=None): if db_path is None: db_path = STATIC_DATA_PATH / "lookups/ska_station_sensitivity_AAVS2.db" self._db_path = db_path
[docs] def lookup_stokes_i_sefd( self, az: float, el: float, start_lst: float, end_lst: float, freq_fine_mhz: np.ndarray, ) -> np.ndarray: """ Look up the SEFD values from the database corresponding to the local coordinates (az, el) between LST start_lst and end_lst. :return: an array of Stokes I SEFD values at frequencies in fine_freq_mhz. """ # Now that we all the relevant boundary conditions for this time cell, # query the DB and get the A-over-T values (labelled as sensitivity) corresponding # to a coarse grid of frequency values from 50 to 350 MHz (35 elements on the coarse grid). freq_elements_per_query = 35 with sqlite3.connect(self._db_path) as sql_connection: # Since end_lst time can wrap around 24h, create an appropriate SQL string if end_lst < start_lst: lst_clause = ( "((lst>=:start_lst AND lst<24.0) OR (lst>=0 AND lst<=:end_lst))" ) else: lst_clause = "(lst>=:start_lst AND lst<=:end_lst)" # Build the params along with the query. These will be passed separately to the psycopg cursor, which avoids the risk of SQL injection. params = {"start_lst": start_lst, "end_lst": end_lst} # The azim_deg and za_deg values in the table increase in 5 degree steps, so in the # query we match to the closest step by checking if the difference is less # than 2.5 degrees rather than using an exact equals. azim_and_za_deg_clause = "ABS(za_deg-(90-:el))<=2.5" params["el"] = el # The zenith is represented in the database as az=za=0 whilst normally there are multiple azimuth values for each zenith angle. # To ensure a result is retured for queries near to the zenith, we only query on azimuth if the source is not close to the zenith. if 90 - el >= 2.5: if az > 357.5: # azim_deg has values in the table for range 0 <= azim_deg < 360, so for az > 357.5 we want # to match azim_deg=0 rather than azim_deg=360 azim_and_za_deg_clause += " AND ABS(azim_deg+(360-:az))<=2.5" else: azim_and_za_deg_clause += " AND ABS(azim_deg-:az)<=2.5" params["az"] = az sql_query = ( "SELECT frequency_mhz,sensitivity " "FROM Sensitivity " "WHERE polarisation=:polarisation AND " f"{azim_and_za_deg_clause} AND {lst_clause}" ) # Fetch X polarization A-over-T fetched_rows_x = sql_connection.execute( sql_query, {**params, "polarisation": "X"} ).fetchall() freq_x, aot_x = list(zip(*fetched_rows_x)) # Fetch Y polarization A-over-T fetched_rows_y = sql_connection.execute( sql_query, {**params, "polarisation": "Y"} ).fetchall() freq_y, aot_y = list(zip(*fetched_rows_y)) # Due to a bug in how the LOW SEFD database was generated, sometimes the # db query returns multiple rows. This is a temporary fix by reading just the first # row. We can get rid of this fix once a new SEFD table is generated during # AAVS3 commissioning. if len(freq_x) > freq_elements_per_query: freq_x = freq_x[:freq_elements_per_query] freq_y = freq_y[:freq_elements_per_query] aot_x = aot_x[:freq_elements_per_query] aot_y = aot_y[:freq_elements_per_query] # Convert A-over-T to SEFD # Note that the Boltzmann constant here has units [Jy m^2 / K] sefd_x_coarse = 2 * 1380.0 / np.asarray(aot_x) sefd_y_coarse = 2 * 1380.0 / np.asarray(aot_y) # Fit a cubic spline to coarse SEFD and interpolate to get # SEFD corresponding to the fine frequency grid. spline_x = UnivariateSpline(freq_x, sefd_x_coarse, s=0) spline_y = UnivariateSpline(freq_y, sefd_y_coarse, s=0) sefd_x_fine = spline_x(freq_fine_mhz) sefd_y_fine = spline_y(freq_fine_mhz) sefd_i_fine = 0.5 * np.sqrt(sefd_x_fine**2 + sefd_y_fine**2) return sefd_i_fine