# -*- mode: python; coding: utf-8 -*
# Copyright (c) 2018 Radio Astronomy Software Group
# Licensed under the 3-clause BSD License
from __future__ import absolute_import, division, print_function
import numpy as np
from astropy.units import Quantity
from astropy.time import Time
from astropy.coordinates import Angle, SkyCoord, EarthLocation, AltAz
[docs]class Source(object):
"""
Defines a single point source at a given ICRS ra/dec coordinate, with a
flux density defined by stokes parameters.
Used to calculate local coherency matrix for source brightness in AltAz
frame at a specified time.
"""
name = None
freq = None
stokes = None
ra = None
dec = None
coherency_radec = None
alt_az = None
def __init__(self, name, ra, dec, freq, stokes, pos_tol=np.finfo(float).eps):
"""
Initialize from source catalog
Args:
name: Unique identifier for the source.
ra: astropy Angle object
source RA in J2000 (or ICRS) coordinates
dec: astropy Angle object
source Dec in J2000 (or ICRS) coordinates
stokes:
4 element vector giving the source [I, Q, U, V]
freq: astropy quantity
frequency of source catalog value
pos_tol: float, defaults to minimum float in numpy
position tolerance in degrees
"""
if not isinstance(ra, Angle):
raise ValueError('ra must be an astropy Angle object. '
'value was: {ra}'.format(ra=ra))
if not isinstance(dec, Angle):
raise ValueError('dec must be an astropy Angle object. '
'value was: {dec}'.format(dec=dec))
if not isinstance(freq, Quantity):
raise ValueError('freq must be an astropy Quantity object. '
'value was: {f}'.format(f=freq))
self.name = name
self.freq = freq
self.stokes = stokes
self.ra = ra
self.dec = dec
self.pos_tol = pos_tol
self.skycoord = SkyCoord(self.ra, self.dec, frame='icrs')
# The coherency is a 2x2 matrix giving electric field correlation in Jy.
# Multiply by .5 to ensure that Trace sums to I not 2*I
self.coherency_radec = .5 * np.array([[self.stokes[0] + self.stokes[1],
self.stokes[2] - 1j * self.stokes[3]],
[self.stokes[2] + 1j * self.stokes[3],
self.stokes[0] - self.stokes[1]]])
[docs] @profile
def coherency_calc(self, time, telescope_location):
"""
Calculate the local coherency in alt/az basis for this source at a time & location.
The coherency is a 2x2 matrix giving electric field correlation in Jy.
It's specified on the object as a coherency in the ra/dec basis,
but must be rotated into local alt/az.
Args:
time: astropy Time object
telescope_location: astropy EarthLocation object
Returns:
local coherency in alt/az basis
"""
if not isinstance(time, Time):
raise ValueError('time must be an astropy Time object. '
'value was: {t}'.format(t=time))
if not isinstance(telescope_location, EarthLocation):
raise ValueError('telescope_location must be an astropy EarthLocation object. '
'value was: {al}'.format(al=telescope_location))
if np.sum(np.abs(self.stokes[1:])) == 0:
rotation_matrix = np.array([[1, 0], [0, 1]])
else:
# First need to calculate the sin & cos of the parallactic angle
# See Meeus's astronomical algorithms eq 14.1
# also see Astroplan.observer.parallactic_angle method
time.location = telescope_location
lst = time.sidereal_time('apparent')
cirs_source_coord = self.skycoord.transform_to('cirs')
tee_ra = simutils.cirs_to_tee_ra(cirs_source_coord.ra, time)
hour_angle = (lst - tee_ra).rad
sinX = np.sin(hour_angle)
cosX = np.tan(telescope_location.lat) * np.cos(self.dec) - np.sin(self.dec) * np.cos(hour_angle)
rotation_matrix = np.array([[cosX, sinX], [-sinX, cosX]])
coherency_local = np.einsum('ab,bc,cd->ad', rotation_matrix.T,
self.coherency_radec, rotation_matrix)
return coherency_local
[docs] @profile
def alt_az_calc(self, time, telescope_location):
"""
calculate the altitude & azimuth for this source at a time & location
Args:
time: astropy Time object
telescope_location: astropy EarthLocation object
Returns:
(altitude, azimuth) in radians
"""
if not isinstance(time, Time):
raise ValueError('time must be an astropy Time object. '
'value was: {t}'.format(t=time))
if not isinstance(telescope_location, EarthLocation):
raise ValueError('telescope_location must be an astropy EarthLocation object. '
'value was: {al}'.format(al=telescope_location))
source_altaz = self.skycoord.transform_to(AltAz(obstime=time, location=telescope_location))
alt_az = (source_altaz.alt.rad, source_altaz.az.rad)
self.alt_az = alt_az
return alt_az
[docs] @profile
def pos_lmn(self, time, telescope_location):
"""
calculate the direction cosines of this source at a time & location
Args:
time: astropy Time object
telescope_location: astropy EarthLocation object
Returns:
(l, m, n) direction cosine values
"""
# calculate direction cosines of source at current time and array location
if self.alt_az is None:
self.alt_az_calc(time, telescope_location)
# Need a horizon mask, for now using pi/2
if self.alt_az[0] < 0:
return None
pos_l = np.sin(self.alt_az[1]) * np.cos(self.alt_az[0])
pos_m = np.cos(self.alt_az[1]) * np.cos(self.alt_az[0])
pos_n = np.sin(self.alt_az[0])
return (pos_l, pos_m, pos_n)
def __eq__(self, other):
return (np.isclose(self.ra.deg, other.ra.deg, atol=self.pos_tol)
and np.isclose(self.dec.deg, other.dec.deg, atol=self.pos_tol)
and np.all(self.stokes == other.stokes)
and (self.name == other.name)
and (self.freq == other.freq))