Skip to content

Commit

Permalink
First slug of updates, removing acceptable range from telescope_loc, …
Browse files Browse the repository at this point in the history
…'fixing' LST checking in objects
  • Loading branch information
kartographer committed Nov 3, 2023
1 parent d63b9a5 commit 0573cc1
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 130 deletions.
19 changes: 1 addition & 18 deletions pyuvdata/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,8 +895,6 @@ class LocationParameter(UVParameter):
"""

_range_dict = {"itrs": (6.35e6, 6.39e6), "mcmf": (1717100.0, 1757100.0)}

def __init__(
self,
name,
Expand All @@ -905,7 +903,7 @@ def __init__(
spoof_val=None,
description="",
frame="itrs",
acceptable_range=-1,
acceptable_range=None,
tols=1e-3,
):
super(LocationParameter, self).__init__(
Expand All @@ -921,21 +919,6 @@ def __init__(
)
self.frame = frame

# If acceptable_range is set, set it now. Has to be done after frame is set
# because setting the frame changes the acceptable_range.
if acceptable_range != -1:
self.acceptable_range = acceptable_range

def __setattr__(self, name, value):
"""Ensure that acceptable_range is set properly when frame is changed."""
if name == "frame":
if value in self._range_dict.keys():
self.acceptable_range = self._range_dict[value]
else:
self.acceptable_range = None

return super().__setattr__(name, value)

def lat_lon_alt(self):
"""Get value in (latitude, longitude, altitude) tuple in radians."""
if self.value is None:
Expand Down
27 changes: 0 additions & 27 deletions pyuvdata/tests/test_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,33 +408,6 @@ def test_location_acceptable_none():
assert param1.check_acceptability()[0]


def test_location_mcmf_acceptability():
loc = np.array([1717200.0, 0.0, 0.0])
param1 = uvp.LocationParameter(name="p2", value=loc, frame="mcmf")
assert param1.acceptable_range == (1717100.0, 1757100.0)
assert param1.check_acceptability()[0]

# test that the acceptable range is changed when the frame is changed
param1.frame = "itrs"
assert param1.acceptable_range == (6.35e6, 6.39e6)
assert not param1.check_acceptability()[0]

param1.frame = "foo"
assert param1.acceptable_range is None
assert param1.check_acceptability()[0]

param1.frame = "mcmf"
assert param1.acceptable_range == (1717100.0, 1757100.0)
assert param1.check_acceptability()[0]

# check that if you set the acceptable range on init that it is used
param1 = uvp.LocationParameter(
name="p2", value=loc, acceptable_range=(1717100.0, 1717150.0), frame="mcmf"
)
assert param1.acceptable_range == (1717100.0, 1717150.0)
assert not param1.check_acceptability()[0]


@pytest.mark.parametrize(
"sky2",
[
Expand Down
92 changes: 92 additions & 0 deletions pyuvdata/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@

# standard angle tolerance: 1 mas in radians.
RADIAN_TOL = 1 * 2 * np.pi * 1e-3 / (60.0 * 60.0 * 360.0)
# standard lst time tolerance: 1 ms (15 mas in radians)
LST_RAD_TOL = 1 * 2 * np.pi * 1e-3 / (86400.0)

# fmt: off
# polarization constants
Expand Down Expand Up @@ -115,6 +117,13 @@
"rr": ["r", "r"], "ll": ["l", "l"],
"rl": ["r", "l"], "lr": ["l", "r"]}

ANGLE_TIME_EQUIV = [(units.s, units.arcsec, lambda x: x * 15.0, lambda x: x / 15.0)]

_range_dict = {
"itrs": (6.35e6, 6.39e6, "Earth"), "mcmf": (1717100.0, 1757100.0, "Earth")
}


# fmt: on


Expand Down Expand Up @@ -4078,6 +4087,89 @@ def check_lsts_against_times(
)


def check_surface_based_positions(
*,
telescope_loc=None,
telescope_frame="itrs",
antenna_positions=None,
raise_error=True,
raise_warning=True,
):
"""
Check that antenna positions are consistent with ground-based values.
Check that the antenna position, telescope location, or combination of both produces
locations that are consistent with surface-based positions. If supplying both
antenna position and telescope location, the check will be run against the sum total
of both. For the Earth, the permitted range of values is betwen 6350 and 6390 km,
whereas for theMoon the range is 1717.1 to 1757.1 km.
telescope_loc : tuple or EarthLocation or MoonLocation
Telescope location, specified as a 3-elemnemt tuple (specifying geocentric
position in meters) or as an astropy EarthLocation (or lunarsky MoonLocation).
telescope_frame : str, optional
Reference frame for latitude/longitude/altitude. Options are itrs (default) or
mcmf. Only used if telescope_loc is not an EarthLocation or MoonLocation.
antenna_positions : ndarray of float
List of antenna positions relative to array center in ECEF coordinates,
required if not providing `uvw_array`. Shape is (Nants, 3). If no telescope_loc
is specified, these values will be assumed to be relative to geocenter.
raise_error : bool
If True, an error is raised if telescope_loc and/or telescope_loc do not conform
to expectations for a surface-based telescope. Default is True.
raise_warning : bool
If True, a warning is raised if telescope_loc and/or telescope_loc do not
conform to expectations for a surface-based telescope. Default is True, only
used if `raise_error` is set to False.
Returns
-------
valid : bool
If True, the antenna_positions and/or telescope_loc conform to expectations for
a surface-based telescope. Otherwise returns false.
"""
if antenna_positions is None:
antenna_positions = np.zeros((1, 3))

if isinstance(telescope_loc, EarthLocation) or (
hasmoon and isinstance(telescope_loc, MoonLocation)
):
antenna_positions = antenna_positions + (
telescope_loc.x.to("m").value,
telescope_loc.y.to("m").value,
telescope_loc.x.to("m").value,
)
elif telescope_loc is not None:
antenna_positions = antenna_positions + telescope_loc

low_lim, hi_lim, world = _range_dict[telescope_frame]

err_type = None
if np.any(np.sum(antenna_positions**2.0, axis=1) < low_lim**2.0):
err_type = "below"
elif np.any(np.sum(antenna_positions**2.0, axis=1) > hi_lim**2.0):
err_type = "above"

if err_type is None:
return True

err_msg = (
f"{telescope_frame} antenna position vector magnitudes must be on the order of "
f"the radius of {world} -- they appear to lie well {err_type} this."
)

# If desired, raise an error
if raise_error:
raise ValueError(err_msg)

# Otherwise, if desired, raise a warning instead
if raise_warning:
warnings.warn(err_msg)

return False


def uvw_track_generator(
*,
lon_coord=None,
Expand Down
8 changes: 1 addition & 7 deletions pyuvdata/uvcal/tests/test_uvcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3990,13 +3990,7 @@ def test_init_from_uvdata(
# of precision in the processing pipeline.
assert uvc_new._time_array == uvc2._time_array
uvc_new.time_array = uvc2.time_array
with uvtest.check_warnings(
UserWarning,
match="The lst_array is not self-consistent with the time_array and "
"telescope location. Consider recomputing with the "
"`set_lsts_from_time_array` method.",
):
uvc_new.check()
uvc_new.check()

uvc_new.set_lsts_from_time_array()

Expand Down
47 changes: 40 additions & 7 deletions pyuvdata/uvcal/uvcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import warnings

import numpy as np
from astropy import units
from docstring_parser import DocstringStyle

from .. import parameter as uvp
Expand Down Expand Up @@ -102,11 +103,7 @@ def __init__(self):
"telescope_location_lat_lon_alt_degrees properties"
)
self._telescope_location = uvp.LocationParameter(
"telescope_location",
description=desc,
acceptable_range=(6.35e6, 6.39e6),
tols=1e-3,
required=True,
"telescope_location", description=desc, tols=1e-3, required=True
)

desc = (
Expand Down Expand Up @@ -1194,7 +1191,11 @@ def _check_freq_spacing(self, raise_errors=True):
)

def check(
self, check_extra=True, run_check_acceptability=True, check_freq_spacing=False
self,
check_extra=True,
run_check_acceptability=True,
check_freq_spacing=False,
lst_tol="1ms",
):
"""
Add some extra checks on top of checks on UVBase class.
Expand All @@ -1212,6 +1213,14 @@ def check(
Option to check if frequencies are evenly spaced and the spacing is
equal to their channel_width. This is not required for UVCal
objects in general but is required to write to calfits files.
lst_tol : str or float or Quantity
Tolerance level at which to test LSTs against their expected values. If
provided as a float, must be in units of radians, otherwise a string
parsable by the astropy `Quantity` class (or a `Quantity` object itself)
with a time-based value can also be used. Default value is 1 millisecond,
which is set by the predictive uncertainty in IERS calculations of DUT1,
which for some observatories sets the precision with which these values
are written. Note that this will raise a warning if the check fails.
Returns
-------
Expand Down Expand Up @@ -1309,14 +1318,38 @@ def check(
self._check_freq_spacing()

if run_check_acceptability:
# Check antenna positions
uvutils.check_surface_based_positions(
antenna_positions=self.antenna_positions,
telescope_loc=self.telescope_location,
telescope_frame=self._telescope_location.frame,
raise_error=False,
)

# Figure out our LST tols
if lst_tol is None:
lst_tols = self._lst_array.tols
else:
if isinstance(lst_tol, str):
lst_tol = units.Quantity(lst_tol)
if isinstance(lst_tol, units.Quantity):
lst_tol = lst_tol.to(
"rad", equivalencies=uvutils.ANGLE_TIME_EQUIV
).value
if not isinstance(lst_tol, float):
raise ValueError(
"lst_tol must be of type float, angle Quantity, or None."
)
lst_tols = [0, lst_tol]

lat, lon, alt = self.telescope_location_lat_lon_alt_degrees
uvutils.check_lsts_against_times(
jd_array=self.time_array,
lst_array=self.lst_array,
latitude=lat,
longitude=lon,
altitude=alt,
lst_tols=self._lst_array.tols,
lst_tols=lst_tols,
frame=self._telescope_location.frame,
)

Expand Down
7 changes: 6 additions & 1 deletion pyuvdata/uvdata/miriad.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,12 @@ def _load_antpos(self, uv, sorted_unique_ants=None, correct_lat_lon=True):
rel_ecef_antpos = ecef_antpos
else:
self.telescope_location = np.mean(ecef_antpos[good_antpos, :], axis=0)
valid_location = self._telescope_location.check_acceptability()[0]
valid_location = uvutils.check_surface_based_positions(
telescope_loc=self.telescope_location,
telescope_frame=self._telescope_location.frame,
raise_error=False,
raise_warning=False,
)

# check to see if this could be a valid telescope_location
if valid_location:
Expand Down
9 changes: 7 additions & 2 deletions pyuvdata/uvdata/tests/test_uvdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -5514,8 +5514,13 @@ def test_telescope_loc_xyz_check(paper_uvh5, tmp_path):
uv.read(fname, run_check=False, use_future_array_shapes=True)

# try to read without checks: assert it fails
with pytest.raises(
ValueError, match="UVParameter _telescope_location has unacceptable values."
with uvtest.check_warnings(
UserWarning,
[
"The uvw_array does not match the expected",
"itrs antenna position vector magnitudes must be on the order "
"of the radius of Earth -- they appear to lie well below this.",
],
):
uv.read(fname, use_future_array_shapes=True)

Expand Down
Loading

0 comments on commit 0573cc1

Please sign in to comment.