diff --git a/astropy/coordinates/angle_formats.py b/astropy/coordinates/angle_formats.py index 512cc1f808c..a811c69aa12 100644 --- a/astropy/coordinates/angle_formats.py +++ b/astropy/coordinates/angle_formats.py @@ -49,6 +49,7 @@ class _AngleParser: This class should not be used directly. Use `parse_angle` instead. """ + # For safe multi-threaded operation all class (but not instance) # members that carry state should be thread-local. They are stored # in the following class member @@ -62,14 +63,15 @@ def __init__(self): # generate the parser for each release (as done for unit formats). # For some discussion of this problem, see # https://github.com/astropy/astropy/issues/5350#issuecomment-248770151 - if '_parser' not in _AngleParser._thread_local.__dict__: - (_AngleParser._thread_local._parser, - _AngleParser._thread_local._lexer) = self._make_parser() + if "_parser" not in _AngleParser._thread_local.__dict__: + ( + _AngleParser._thread_local._parser, + _AngleParser._thread_local._lexer, + ) = self._make_parser() @classmethod def _get_simple_unit_names(cls): - simple_units = set( - u.radian.find_equivalent_units(include_prefix_units=True)) + simple_units = set(u.radian.find_equivalent_units(include_prefix_units=True)) simple_unit_names = set() # We filter out degree and hourangle, since those are treated # separately. @@ -82,88 +84,88 @@ def _get_simple_unit_names(cls): def _make_parser(cls): # List of token names. tokens = ( - 'SIGN', - 'UINT', - 'UFLOAT', - 'COLON', - 'DEGREE', - 'HOUR', - 'MINUTE', - 'SECOND', - 'SIMPLE_UNIT', - 'EASTWEST', - 'NORTHSOUTH' + "SIGN", + "UINT", + "UFLOAT", + "COLON", + "DEGREE", + "HOUR", + "MINUTE", + "SECOND", + "SIMPLE_UNIT", + "EASTWEST", + "NORTHSOUTH", ) # NOTE THE ORDERING OF THESE RULES IS IMPORTANT!! # Regular expression rules for simple tokens def t_UFLOAT(t): - r'((\d+\.\d*)|(\.\d+))([eE][+-−]?\d+)?' + r"((\d+\.\d*)|(\.\d+))([eE][+-−]?\d+)?" # The above includes Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. - t.value = float(t.value.replace('−', '-')) + t.value = float(t.value.replace("−", "-")) return t def t_UINT(t): - r'\d+' + r"\d+" t.value = int(t.value) return t def t_SIGN(t): - r'[+−-]' + r"[+−-]" # The above include Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. - if t.value == '+': + if t.value == "+": t.value = 1.0 else: t.value = -1.0 return t def t_EASTWEST(t): - r'[EW]$' - t.value = -1.0 if t.value == 'W' else 1.0 + r"[EW]$" + t.value = -1.0 if t.value == "W" else 1.0 return t def t_NORTHSOUTH(t): - r'[NS]$' + r"[NS]$" # We cannot use lower-case letters otherwise we'll confuse # s[outh] with s[econd] - t.value = -1.0 if t.value == 'S' else 1.0 + t.value = -1.0 if t.value == "S" else 1.0 return t def t_SIMPLE_UNIT(t): t.value = u.Unit(t.value) return t - t_SIMPLE_UNIT.__doc__ = '|'.join( - f'(?:{x})' for x in cls._get_simple_unit_names()) + t_SIMPLE_UNIT.__doc__ = "|".join( + f"(?:{x})" for x in cls._get_simple_unit_names() + ) - t_COLON = ':' - t_DEGREE = r'd(eg(ree(s)?)?)?|°' - t_HOUR = r'hour(s)?|h(r)?|ʰ' - t_MINUTE = r'm(in(ute(s)?)?)?|′|\'|ᵐ' - t_SECOND = r's(ec(ond(s)?)?)?|″|\"|ˢ' + t_COLON = ":" + t_DEGREE = r"d(eg(ree(s)?)?)?|°" + t_HOUR = r"hour(s)?|h(r)?|ʰ" + t_MINUTE = r"m(in(ute(s)?)?)?|′|\'|ᵐ" + t_SECOND = r"s(ec(ond(s)?)?)?|″|\"|ˢ" # A string containing ignored characters (spaces) - t_ignore = ' ' + t_ignore = " " # Error handling rule def t_error(t): - raise ValueError( - f"Invalid character at col {t.lexpos}") + raise ValueError(f"Invalid character at col {t.lexpos}") - lexer = parsing.lex(lextab='angle_lextab', package='astropy/coordinates') + lexer = parsing.lex(lextab="angle_lextab", package="astropy/coordinates") def p_angle(p): - ''' + """ angle : sign hms eastwest | sign dms dir | sign arcsecond dir | sign arcminute dir | sign simple dir - ''' + """ sign = p[1] * p[3] value, unit = p[2] if isinstance(value, tuple): @@ -172,73 +174,73 @@ def p_angle(p): p[0] = (sign * value, unit) def p_sign(p): - ''' + """ sign : SIGN | - ''' + """ if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_eastwest(p): - ''' + """ eastwest : EASTWEST | - ''' + """ if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_dir(p): - ''' + """ dir : EASTWEST | NORTHSOUTH | - ''' + """ if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_ufloat(p): - ''' + """ ufloat : UFLOAT | UINT - ''' + """ p[0] = p[1] def p_colon(p): - ''' + """ colon : UINT COLON ufloat | UINT COLON UINT COLON ufloat - ''' + """ if len(p) == 4: p[0] = (p[1], p[3]) elif len(p) == 6: p[0] = (p[1], p[3], p[5]) def p_spaced(p): - ''' + """ spaced : UINT ufloat | UINT UINT ufloat - ''' + """ if len(p) == 3: p[0] = (p[1], p[2]) elif len(p) == 4: p[0] = (p[1], p[2], p[3]) def p_generic(p): - ''' + """ generic : colon | spaced | ufloat - ''' + """ p[0] = p[1] def p_hms(p): - ''' + """ hms : UINT HOUR | UINT HOUR ufloat | UINT HOUR UINT MINUTE @@ -246,7 +248,7 @@ def p_hms(p): | UINT HOUR UINT MINUTE ufloat | UINT HOUR UINT MINUTE ufloat SECOND | generic HOUR - ''' + """ if len(p) == 3: p[0] = (p[1], u.hourangle) elif len(p) in (4, 5): @@ -255,7 +257,7 @@ def p_hms(p): p[0] = ((p[1], p[3], p[5]), u.hourangle) def p_dms(p): - ''' + """ dms : UINT DEGREE | UINT DEGREE ufloat | UINT DEGREE UINT MINUTE @@ -263,7 +265,7 @@ def p_dms(p): | UINT DEGREE UINT MINUTE ufloat | UINT DEGREE UINT MINUTE ufloat SECOND | generic DEGREE - ''' + """ if len(p) == 3: p[0] = (p[1], u.degree) elif len(p) in (4, 5): @@ -272,44 +274,44 @@ def p_dms(p): p[0] = ((p[1], p[3], p[5]), u.degree) def p_simple(p): - ''' + """ simple : generic | generic SIMPLE_UNIT - ''' + """ if len(p) == 2: p[0] = (p[1], None) else: p[0] = (p[1], p[2]) def p_arcsecond(p): - ''' + """ arcsecond : generic SECOND - ''' + """ p[0] = (p[1], u.arcsecond) def p_arcminute(p): - ''' + """ arcminute : generic MINUTE - ''' + """ p[0] = (p[1], u.arcminute) def p_error(p): raise ValueError - parser = parsing.yacc(tabmodule='angle_parsetab', package='astropy/coordinates') + parser = parsing.yacc(tabmodule="angle_parsetab", package="astropy/coordinates") return parser, lexer def parse(self, angle, unit, debug=False): try: found_angle, found_unit = self._thread_local._parser.parse( - angle, lexer=self._thread_local._lexer, debug=debug) + angle, lexer=self._thread_local._lexer, debug=debug + ) except ValueError as e: if str(e): raise ValueError(f"{str(e)} in angle {angle!r}") from e else: - raise ValueError( - f"Syntax error parsing angle {angle!r}") from e + raise ValueError(f"Syntax error parsing angle {angle!r}") from e if unit is None and found_unit is None: raise u.UnitsError("No unit specified") @@ -321,9 +323,9 @@ def _check_hour_range(hrs): """ Checks that the given value is in the range (-24, 24). """ - if np.any(np.abs(hrs) == 24.): - warn(IllegalHourWarning(hrs, 'Treating as 24 hr')) - elif np.any(hrs < -24.) or np.any(hrs > 24.): + if np.any(np.abs(hrs) == 24.0): + warn(IllegalHourWarning(hrs, "Treating as 24 hr")) + elif np.any(hrs < -24.0) or np.any(hrs > 24.0): raise IllegalHourError(hrs) @@ -332,9 +334,9 @@ def _check_minute_range(m): Checks that the given value is in the range [0,60]. If the value is equal to 60, then a warning is raised. """ - if np.any(m == 60.): - warn(IllegalMinuteWarning(m, 'Treating as 0 min, +1 hr/deg')) - elif np.any(m < -60.) or np.any(m > 60.): + if np.any(m == 60.0): + warn(IllegalMinuteWarning(m, "Treating as 0 min, +1 hr/deg")) + elif np.any(m < -60.0) or np.any(m > 60.0): # "Error: minutes not in range [-60,60) ({0}).".format(min)) raise IllegalMinuteError(m) @@ -344,11 +346,11 @@ def _check_second_range(sec): Checks that the given value is in the range [0,60]. If the value is equal to 60, then a warning is raised. """ - if np.any(sec == 60.): - warn(IllegalSecondWarning(sec, 'Treating as 0 sec, +1 min')) + if np.any(sec == 60.0): + warn(IllegalSecondWarning(sec, "Treating as 0 sec, +1 min")) elif sec is None: pass - elif np.any(sec < -60.) or np.any(sec > 60.): + elif np.any(sec < -60.0) or np.any(sec > 60.0): # "Error: seconds not in range [-60,60) ({0}).".format(sec)) raise IllegalSecondError(sec) @@ -406,16 +408,20 @@ def degrees_to_dms(d): sign = np.copysign(1.0, d) (df, d) = np.modf(np.abs(d)) # (degree fraction, degree) - (mf, m) = np.modf(df * 60.) # (minute fraction, minute) - s = mf * 60. + (mf, m) = np.modf(df * 60.0) # (minute fraction, minute) + s = mf * 60.0 return np.floor(sign * d), sign * np.floor(m), sign * s -@deprecated("dms_to_degrees (or creating an Angle with a tuple) has ambiguous " - "behavior when the degree value is 0", - alternative="another way of creating angles instead (e.g. a less " - "ambiguous string like '-0d1m2.3s'") +@deprecated( + "dms_to_degrees (or creating an Angle with a tuple) has ambiguous " + "behavior when the degree value is 0", + alternative=( + "another way of creating angles instead (e.g. a less " + "ambiguous string like '-0d1m2.3s'" + ), +) def dms_to_degrees(d, m, s=None): """ Convert degrees, arcminute, arcsecond to a float degrees value. @@ -435,17 +441,27 @@ def dms_to_degrees(d, m, s=None): m = np.floor(np.abs(m)) s = np.abs(s) except ValueError as err: - raise ValueError(format_exception( - "{func}: dms values ({1[0]},{2[1]},{3[2]}) could not be " - "converted to numbers.", d, m, s)) from err - - return sign * (d + m / 60. + s / 3600.) - - -@deprecated("hms_to_hours (or creating an Angle with a tuple) has ambiguous " - "behavior when the hour value is 0", - alternative="another way of creating angles instead (e.g. a less " - "ambiguous string like '-0h1m2.3s'") + raise ValueError( + format_exception( + "{func}: dms values ({1[0]},{2[1]},{3[2]}) could not be " + "converted to numbers.", + d, + m, + s, + ) + ) from err + + return sign * (d + m / 60.0 + s / 3600.0) + + +@deprecated( + "hms_to_hours (or creating an Angle with a tuple) has ambiguous " + "behavior when the hour value is 0", + alternative=( + "another way of creating angles instead (e.g. a less " + "ambiguous string like '-0h1m2.3s'" + ), +) def hms_to_hours(h, m, s=None): """ Convert hour, minute, second to a float hour value. @@ -465,11 +481,17 @@ def hms_to_hours(h, m, s=None): m = np.floor(np.abs(m)) s = np.abs(s) except ValueError as err: - raise ValueError(format_exception( - "{func}: HMS values ({1[0]},{2[1]},{3[2]}) could not be " - "converted to numbers.", h, m, s)) from err + raise ValueError( + format_exception( + "{func}: HMS values ({1[0]},{2[1]},{3[2]}) could not be " + "converted to numbers.", + h, + m, + s, + ) + ) from err - return sign * (h + m / 60. + s / 3600.) + return sign * (h + m / 60.0 + s / 3600.0) def hms_to_degrees(h, m, s): @@ -477,7 +499,7 @@ def hms_to_degrees(h, m, s): Convert hour, minute, second to a float degrees value. """ - return hms_to_hours(h, m, s) * 15. + return hms_to_hours(h, m, s) * 15.0 def hms_to_radians(h, m, s): @@ -502,6 +524,7 @@ def hours_to_decimal(h): Convert any parseable hour value into a float value. """ from . import angles + return angles.Angle(h, unit=u.hourangle).hour @@ -561,8 +584,7 @@ def radians_to_dms(r): return degrees_to_dms(degrees) -def sexagesimal_to_string(values, precision=None, pad=False, sep=(':',), - fields=3): +def sexagesimal_to_string(values, precision=None, pad=False, sep=(":",), fields=3): """ Given an already separated tuple of sexagesimal values, returns a string. @@ -590,32 +612,32 @@ def sexagesimal_to_string(values, precision=None, pad=False, sep=(':',), sep = tuple(sep) if fields < 1 or fields > 3: - raise ValueError( - "fields must be 1, 2, or 3") + raise ValueError("fields must be 1, 2, or 3") if not sep: # empty string, False, or None, etc. - sep = ('', '', '') + sep = ("", "", "") elif len(sep) == 1: if fields == 3: - sep = sep + (sep[0], '') + sep = sep + (sep[0], "") elif fields == 2: - sep = sep + ('', '') + sep = sep + ("", "") else: - sep = ('', '', '') + sep = ("", "", "") elif len(sep) == 2: - sep = sep + ('',) + sep = sep + ("",) elif len(sep) != 3: raise ValueError( - "Invalid separator specification for converting angle to string.") + "Invalid separator specification for converting angle to string." + ) # Simplify the expression based on the requested precision. For # example, if the seconds will round up to 60, we should convert # it to 0 and carry upwards. If the field is hidden (by the # fields kwarg) we round up around the middle, 30.0. if precision is None: - rounding_thresh = 60.0 - (10.0 ** -8) + rounding_thresh = 60.0 - (10.0**-8) else: - rounding_thresh = 60.0 - (10.0 ** -precision) + rounding_thresh = 60.0 - (10.0**-precision) if fields == 3 and values[2] >= rounding_thresh: values[2] = 0.0 @@ -630,29 +652,31 @@ def sexagesimal_to_string(values, precision=None, pad=False, sep=(':',), values[0] += 1.0 literal = [] - last_value = '' - literal.append('{0:0{pad}.0f}{sep[0]}') + last_value = "" + literal.append("{0:0{pad}.0f}{sep[0]}") if fields >= 2: - literal.append('{1:02d}{sep[1]}') + literal.append("{1:02d}{sep[1]}") if fields == 3: if precision is None: - last_value = f'{abs(values[2]):.8f}' - last_value = last_value.rstrip('0').rstrip('.') + last_value = f"{abs(values[2]):.8f}" + last_value = last_value.rstrip("0").rstrip(".") else: - last_value = '{0:.{precision}f}'.format( - abs(values[2]), precision=precision) - if len(last_value) == 1 or last_value[1] == '.': - last_value = '0' + last_value - literal.append('{last_value}{sep[2]}') - literal = ''.join(literal) - return literal.format(np.copysign(values[0], sign), - int(values[1]), values[2], - sep=sep, pad=pad, - last_value=last_value) - - -def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's'), - fields=3): + last_value = "{0:.{precision}f}".format(abs(values[2]), precision=precision) + if len(last_value) == 1 or last_value[1] == ".": + last_value = "0" + last_value + literal.append("{last_value}{sep[2]}") + literal = "".join(literal) + return literal.format( + np.copysign(values[0], sign), + int(values[1]), + values[2], + sep=sep, + pad=pad, + last_value=last_value, + ) + + +def hours_to_string(h, precision=5, pad=False, sep=("h", "m", "s"), fields=3): """ Takes a decimal hour value and returns a string formatted as hms with separator specified by the 'sep' parameter. @@ -660,11 +684,12 @@ def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's'), ``h`` must be a scalar. """ h, m, s = hours_to_hms(h) - return sexagesimal_to_string((h, m, s), precision=precision, pad=pad, - sep=sep, fields=fields) + return sexagesimal_to_string( + (h, m, s), precision=precision, pad=pad, sep=sep, fields=fields + ) -def degrees_to_string(d, precision=5, pad=False, sep=':', fields=3): +def degrees_to_string(d, precision=5, pad=False, sep=":", fields=3): """ Takes a decimal hour value and returns a string formatted as dms with separator specified by the 'sep' parameter. @@ -672,5 +697,6 @@ def degrees_to_string(d, precision=5, pad=False, sep=':', fields=3): ``d`` must be a scalar. """ d, m, s = degrees_to_dms(d) - return sexagesimal_to_string((d, m, s), precision=precision, pad=pad, - sep=sep, fields=fields) + return sexagesimal_to_string( + (d, m, s), precision=precision, pad=pad, sep=sep, fields=fields + ) diff --git a/astropy/coordinates/angle_utilities.py b/astropy/coordinates/angle_utilities.py index 1d5ebaead1a..27f81efe8f3 100644 --- a/astropy/coordinates/angle_utilities.py +++ b/astropy/coordinates/angle_utilities.py @@ -4,9 +4,14 @@ used internally in astropy.coordinates.angles, and of possible """ -__all__ = ['angular_separation', 'position_angle', 'offset_by', - 'golden_spiral_grid', 'uniform_spherical_random_surface', - 'uniform_spherical_random_volume'] +__all__ = [ + "angular_separation", + "position_angle", + "offset_by", + "golden_spiral_grid", + "uniform_spherical_random_surface", + "uniform_spherical_random_volume", +] # Third-party import numpy as np @@ -18,6 +23,8 @@ UnitSphericalRepresentation, ) +_TWOPI = 2 * np.pi + def angular_separation(lon1, lat1, lon2, lat2): """ @@ -85,7 +92,7 @@ def position_angle(lon1, lat1, lon2, lat2): x = np.sin(lat2) * np.cos(lat1) - colat * np.sin(lat1) * np.cos(deltalon) y = np.sin(deltalon) * colat - return Angle(np.arctan2(y, x), u.radian).wrap_at(360*u.deg) + return Angle(np.arctan2(y, x), u.radian).wrap_at(360 * u.deg) def offset_by(lon, lat, posang, distance): @@ -139,7 +146,7 @@ def offset_by(lon, lat, posang, distance): small_sin_c = sin_c < 1e-12 if small_sin_c.any(): # For south pole (cos_c = -1), A = posang; for North pole, A=180 deg - posang - A_pole = (90*u.deg + cos_c*(90*u.deg-Angle(posang, u.radian))).to(u.rad) + A_pole = (90 * u.deg + cos_c * (90 * u.deg - Angle(posang, u.radian))).to(u.rad) if A.shape: # broadcast to ensure the shape is like that of A, which is also # affected by the (possible) shapes of lat, posang, and distance. @@ -148,7 +155,7 @@ def offset_by(lon, lat, posang, distance): else: A = A_pole - outlon = (Angle(lon, u.radian) + A).wrap_at(360.0*u.deg).to(u.deg) + outlon = (Angle(lon, u.radian) + A).wrap_at(360.0 * u.deg).to(u.deg) outlat = Angle(np.arcsin(cos_b), u.radian).to(u.deg) return outlon, outlat @@ -175,7 +182,7 @@ def golden_spiral_grid(size): golden_r = (1 + 5**0.5) / 2 grid = np.arange(0, size, dtype=float) + 0.5 - lon = 2*np.pi / golden_r * grid * u.rad + lon = _TWOPI / golden_r * grid * u.rad lat = np.arcsin(1 - 2 * grid / size) * u.rad return UnitSphericalRepresentation(lon, lat) @@ -197,7 +204,7 @@ def uniform_spherical_random_surface(size=1): rng = np.random # can maybe switch to this being an input later - see #11628 - lon = rng.uniform(0, 2*np.pi, size) * u.rad + lon = rng.uniform(0, _TWOPI, size) * u.rad lat = np.arcsin(rng.uniform(-1, 1, size=size)) * u.rad return UnitSphericalRepresentation(lon, lat) @@ -232,17 +239,30 @@ def uniform_spherical_random_volume(size=1, max_radius=1): # # below here can be deleted in v5.0 from astropy.utils.decorators import deprecated -__old_angle_utilities_funcs = ['check_hms_ranges', 'degrees_to_dms', - 'degrees_to_string', 'dms_to_degrees', - 'format_exception', 'hms_to_degrees', - 'hms_to_dms', 'hms_to_hours', - 'hms_to_radians', 'hours_to_decimal', - 'hours_to_hms', 'hours_to_radians', - 'hours_to_string', 'parse_angle', - 'radians_to_degrees', 'radians_to_dms', - 'radians_to_hms', 'radians_to_hours', - 'sexagesimal_to_string'] +__old_angle_utilities_funcs = [ + "check_hms_ranges", + "degrees_to_dms", + "degrees_to_string", + "dms_to_degrees", + "format_exception", + "hms_to_degrees", + "hms_to_dms", + "hms_to_hours", + "hms_to_radians", + "hours_to_decimal", + "hours_to_hms", + "hours_to_radians", + "hours_to_string", + "parse_angle", + "radians_to_degrees", + "radians_to_dms", + "radians_to_hms", + "radians_to_hours", + "sexagesimal_to_string", +] for funcname in __old_angle_utilities_funcs: - vars()[funcname] = deprecated(name='astropy.coordinates.angle_utilities.' + funcname, - alternative='astropy.coordinates.angle_formats.' + funcname, - since='v4.3')(getattr(angle_formats, funcname)) + vars()[funcname] = deprecated( + name="astropy.coordinates.angle_utilities." + funcname, + alternative="astropy.coordinates.angle_formats." + funcname, + since="v4.3", + )(getattr(angle_formats, funcname)) diff --git a/astropy/coordinates/angles.py b/astropy/coordinates/angles.py index 223c55cf9fa..b05eb4e745c 100644 --- a/astropy/coordinates/angles.py +++ b/astropy/coordinates/angles.py @@ -15,13 +15,13 @@ from . import angle_formats as form -__all__ = ['Angle', 'Latitude', 'Longitude'] +__all__ = ["Angle", "Latitude", "Longitude"] # these are used by the `hms` and `dms` attributes -hms_tuple = namedtuple('hms_tuple', ('h', 'm', 's')) -dms_tuple = namedtuple('dms_tuple', ('d', 'm', 's')) -signed_dms_tuple = namedtuple('signed_dms_tuple', ('sign', 'd', 'm', 's')) +hms_tuple = namedtuple("hms_tuple", ("h", "m", "s")) +dms_tuple = namedtuple("dms_tuple", ("d", "m", "s")) +signed_dms_tuple = namedtuple("signed_dms_tuple", ("sign", "d", "m", "s")) class Angle(u.SpecificTypeQuantity): @@ -102,11 +102,11 @@ class Angle(u.SpecificTypeQuantity): `~astropy.units.UnitsError` If a unit is not provided or it is not an angular unit. """ + _equivalent_unit = u.radian _include_easy_conversion_members = True def __new__(cls, angle, unit=None, dtype=np.inexact, copy=True, **kwargs): - if not isinstance(angle, u.Quantity): if unit is not None: unit = cls._convert_unit_to_angle_unit(u.Unit(unit)) @@ -123,10 +123,10 @@ def __new__(cls, angle, unit=None, dtype=np.inexact, copy=True, **kwargs): if angle_unit == u.hourangle: form._check_hour_range(angle[0]) form._check_minute_range(angle[1]) - a = np.abs(angle[0]) + angle[1] / 60. + a = np.abs(angle[0]) + angle[1] / 60.0 if len(angle) == 3: form._check_second_range(angle[2]) - a += angle[2] / 3600. + a += angle[2] / 3600.0 angle = np.copysign(a, angle[0]) @@ -134,13 +134,12 @@ def __new__(cls, angle, unit=None, dtype=np.inexact, copy=True, **kwargs): # Possible conversion to `unit` will be done below. angle = u.Quantity(angle, angle_unit, copy=False) - elif (isiterable(angle) and - not (isinstance(angle, np.ndarray) and - angle.dtype.kind not in 'SUVO')): + elif isiterable(angle) and not ( + isinstance(angle, np.ndarray) and angle.dtype.kind not in "SUVO" + ): angle = [Angle(x, unit, copy=False) for x in angle] - return super().__new__(cls, angle, unit, dtype=dtype, copy=copy, - **kwargs) + return super().__new__(cls, angle, unit, dtype=dtype, copy=copy, **kwargs) @staticmethod def _tuple_to_float(angle, unit): @@ -196,13 +195,22 @@ def signed_dms(self): This is primarily intended for use with `dms` to generate string representations of coordinates that are correct for negative angles. """ - return signed_dms_tuple(np.sign(self.degree), - *form.degrees_to_dms(np.abs(self.degree))) - - def to_string(self, unit=None, decimal=False, sep='fromunit', - precision=None, alwayssign=False, pad=False, - fields=3, format=None): - """ A string representation of the angle. + return signed_dms_tuple( + np.sign(self.degree), *form.degrees_to_dms(np.abs(self.degree)) + ) + + def to_string( + self, + unit=None, + decimal=False, + sep="fromunit", + precision=None, + alwayssign=False, + pad=False, + fields=3, + format=None, + ): + """A string representation of the angle. Parameters ---------- @@ -277,41 +285,44 @@ def to_string(self, unit=None, decimal=False, sep='fromunit', unit = self._convert_unit_to_angle_unit(u.Unit(unit)) separators = { - 'generic': { - u.degree: 'dms', - u.hourangle: 'hms'}, - 'latex': { - u.degree: [r'^\circ', r'{}^\prime', r'{}^{\prime\prime}'], - u.hourangle: [r'^{\mathrm{h}}', r'^{\mathrm{m}}', r'^{\mathrm{s}}']}, - 'unicode': { - u.degree: '°′″', - u.hourangle: 'ʰᵐˢ'} + "generic": {u.degree: "dms", u.hourangle: "hms"}, + "latex": { + u.degree: [r"^\circ", r"{}^\prime", r"{}^{\prime\prime}"], + u.hourangle: [r"^{\mathrm{h}}", r"^{\mathrm{m}}", r"^{\mathrm{s}}"], + }, + "unicode": {u.degree: "°′″", u.hourangle: "ʰᵐˢ"}, } # 'latex_inline' provides no functionality beyond what 'latex' offers, # but it should be implemented to avoid ValueErrors in user code. - separators['latex_inline'] = separators['latex'] + separators["latex_inline"] = separators["latex"] # Default separators are as for generic. - separators[None] = separators['generic'] + separators[None] = separators["generic"] # Create an iterator so we can format each element of what # might be an array. - if not decimal and (unit_is_deg := unit == u.degree - or unit == u.hourangle): + if not decimal and (unit_is_deg := unit == u.degree or unit == u.hourangle): # Sexagesimal. - if sep == 'fromunit': + if sep == "fromunit": if format not in separators: raise ValueError(f"Unknown format '{format}'") sep = separators[format][unit] func = functools.partial( form.degrees_to_string if unit_is_deg else form.hours_to_string, - precision=precision, sep=sep, pad=pad, fields=fields) + precision=precision, + sep=sep, + pad=pad, + fields=fields, + ) else: - if sep != 'fromunit': - raise ValueError(f"'{unit}' can not be represented in sexagesimal notation") + if sep != "fromunit": + raise ValueError( + f"'{unit}' can not be represented in sexagesimal notation" + ) func = ("{:g}" if precision is None else f"{{0:0.{precision}f}}").format - if not (decimal and format is None): # Don't add unit by default for decimal. + # Don't add unit by default for decimal. + if not (decimal and format is None): unit_string = unit.to_string(format=format) - if format == 'latex' or format == 'latex_inline': + if format == "latex" or format == "latex_inline": unit_string = unit_string[1:-1] format_func = func func = lambda x: format_func(x) + unit_string @@ -321,16 +332,16 @@ def do_format(val): # a hexagesimal string. if not np.isnan(val): s = func(float(val)) - if alwayssign and not s.startswith('-'): - s = '+' + s - if format == 'latex' or format == 'latex_inline': - s = f'${s}$' + if alwayssign and not s.startswith("-"): + s = "+" + s + if format == "latex" or format == "latex_inline": + s = f"${s}$" return s s = f"{val}" return s values = self.to_value(unit) - format_ufunc = np.vectorize(do_format, otypes=['U']) + format_ufunc = np.vectorize(do_format, otypes=["U"]) result = format_ufunc(values) if result.ndim == 0: @@ -352,7 +363,7 @@ def _wrap_at(self, wrap_angle): # Do the wrapping, but only if any angles need to be wrapped # # Catch any invalid warnings from the floor division. - with np.errstate(invalid='ignore'): + with np.errstate(invalid="ignore"): wraps = (self_angle - wrap_angle_floor) // a360 valid = np.isfinite(wraps) & (wraps != 0) if np.any(valid): @@ -455,13 +466,13 @@ def _str_helper(self, format=None): def formatter(x): return x.to_string(format=format) - return np.array2string(self, formatter={'all': formatter}) + return np.array2string(self, formatter={"all": formatter}) def __str__(self): return self._str_helper() def _repr_latex_(self): - return self._str_helper(format='latex') + return self._str_helper(format="latex") def _no_angle_subclass(obj): @@ -522,6 +533,7 @@ class Latitude(Angle): `TypeError` If the angle parameter is an instance of :class:`~astropy.coordinates.Longitude`. """ + def __new__(cls, angle, unit=None, **kwargs): # Forbid creating a Lat from a Long. if isinstance(angle, Longitude): @@ -550,11 +562,12 @@ def _validate_angles(self, angles=None): else: limit = u.degree.to(angles.unit, 90.0) - invalid_angles = (np.any(angles.value < -limit) - or np.any(angles.value > limit)) + invalid_angles = np.any(angles.value < -limit) or np.any(angles.value > limit) if invalid_angles: - raise ValueError('Latitude angle(s) must be within -90 deg <= angle <= 90 deg, ' - 'got {}'.format(angles.to(u.degree))) + raise ValueError( + "Latitude angle(s) must be within -90 deg <= angle <= 90 deg, " + f"got {angles.to(u.degree)}" + ) def __setitem__(self, item, value): # Forbid assigning a Long to a Lat. @@ -572,7 +585,7 @@ def __array_ufunc__(self, *args, **kwargs): class LongitudeInfo(u.QuantityInfo): - _represent_as_dict_attrs = u.QuantityInfo._represent_as_dict_attrs + ('wrap_angle',) + _represent_as_dict_attrs = u.QuantityInfo._represent_as_dict_attrs + ("wrap_angle",) class Longitude(Angle): @@ -636,11 +649,12 @@ class Longitude(Angle): def __new__(cls, angle, unit=None, wrap_angle=None, **kwargs): # Forbid creating a Long from a Lat. if isinstance(angle, Latitude): - raise TypeError("A Longitude angle cannot be created from " - "a Latitude angle.") + raise TypeError( + "A Longitude angle cannot be created from a Latitude angle." + ) self = super().__new__(cls, angle, unit=unit, **kwargs) if wrap_angle is None: - wrap_angle = getattr(angle, 'wrap_angle', self._default_wrap_angle) + wrap_angle = getattr(angle, "wrap_angle", self._default_wrap_angle) self.wrap_angle = wrap_angle # angle-like b/c property setter return self @@ -662,8 +676,7 @@ def wrap_angle(self, value): def __array_finalize__(self, obj): super().__array_finalize__(obj) - self._wrap_angle = getattr(obj, '_wrap_angle', - self._default_wrap_angle) + self._wrap_angle = getattr(obj, "_wrap_angle", self._default_wrap_angle) # Any calculation should drop to Angle def __array_ufunc__(self, *args, **kwargs): diff --git a/astropy/coordinates/attributes.py b/astropy/coordinates/attributes.py index 0070d5cc200..61093e99ac4 100644 --- a/astropy/coordinates/attributes.py +++ b/astropy/coordinates/attributes.py @@ -7,10 +7,15 @@ from astropy import units as u from astropy.utils import ShapedLikeNDArray -__all__ = ['Attribute', 'TimeAttribute', 'QuantityAttribute', - 'EarthLocationAttribute', 'CoordinateAttribute', - 'CartesianRepresentationAttribute', - 'DifferentialAttribute'] +__all__ = [ + "Attribute", + "TimeAttribute", + "QuantityAttribute", + "EarthLocationAttribute", + "CoordinateAttribute", + "CartesianRepresentationAttribute", + "DifferentialAttribute", +] class Attribute: @@ -48,9 +53,9 @@ class FK4(BaseCoordinateFrame): ``default is None`` and no value was supplied during initialization. """ - name = '' + name = "" - def __init__(self, default=None, secondary_attribute=''): + def __init__(self, default=None, secondary_attribute=""): self.default = default self.secondary_attribute = secondary_attribute super().__init__() @@ -96,38 +101,42 @@ def __get__(self, instance, frame_cls=None): if instance is None: out = self.default else: - out = getattr(instance, '_' + self.name, self.default) + out = getattr(instance, "_" + self.name, self.default) if out is None: out = getattr(instance, self.secondary_attribute, self.default) out, converted = self.convert_input(out) if instance is not None: - instance_shape = getattr(instance, 'shape', None) # None if instance (frame) has no data! - if instance_shape is not None and (getattr(out, 'shape', ()) and - out.shape != instance_shape): + # None if instance (frame) has no data! + instance_shape = getattr(instance, "shape", None) + if instance_shape is not None and ( + getattr(out, "shape", ()) and out.shape != instance_shape + ): # If the shapes do not match, try broadcasting. try: if isinstance(out, ShapedLikeNDArray): - out = out._apply(np.broadcast_to, shape=instance_shape, - subok=True) + out = out._apply( + np.broadcast_to, shape=instance_shape, subok=True + ) else: out = np.broadcast_to(out, instance_shape, subok=True) except ValueError: # raise more informative exception. raise ValueError( - "attribute {} should be scalar or have shape {}, " - "but is has shape {} and could not be broadcast." - .format(self.name, instance_shape, out.shape)) + f"attribute {self.name} should be scalar or have shape" + f" {instance_shape}, but it has shape {out.shape} and could not" + " be broadcast." + ) converted = True if converted: - setattr(instance, '_' + self.name, out) + setattr(instance, "_" + self.name, out) return out def __set__(self, instance, val): - raise AttributeError('Cannot set frame attribute') + raise AttributeError("Cannot set frame attribute") class TimeAttribute(Attribute): @@ -179,8 +188,7 @@ def convert_input(self, value): try: out = Time(value) except Exception as err: - raise ValueError( - f'Invalid time input {self.name}={value!r}.') from err + raise ValueError(f"Invalid time input {self.name}={value!r}.") from err converted = True # Set attribute as read-only for arrays (not allowed by numpy @@ -206,7 +214,7 @@ class CartesianRepresentationAttribute(Attribute): unit-checking or conversion is performed """ - def __init__(self, default=None, secondary_attribute='', unit=None): + def __init__(self, default=None, secondary_attribute="", unit=None): super().__init__(default, secondary_attribute) self.unit = unit @@ -233,21 +241,26 @@ def convert_input(self, value): If the input is not valid for this attribute. """ - if (isinstance(value, list) and len(value) == 3 and - all(v == 0 for v in value) and self.unit is not None): + if ( + isinstance(value, list) + and len(value) == 3 + and all(v == 0 for v in value) + and self.unit is not None + ): return CartesianRepresentation(np.zeros(3) * self.unit), True else: # is it a CartesianRepresentation with correct unit? - if hasattr(value, 'xyz') and value.xyz.unit == self.unit: + if hasattr(value, "xyz") and value.xyz.unit == self.unit: return value, False converted = True # if it's a CartesianRepresentation, get the xyz Quantity - value = getattr(value, 'xyz', value) - if not hasattr(value, 'unit'): - raise TypeError('tried to set a {} with something that does ' - 'not have a unit.' - .format(self.__class__.__name__)) + value = getattr(value, "xyz", value) + if not hasattr(value, "unit"): + raise TypeError( + f"tried to set a {self.__class__.__name__} with something that does" + " not have a unit." + ) value = value.to(self.unit) @@ -280,13 +293,12 @@ class QuantityAttribute(Attribute): If given, specifies the shape the attribute must be """ - def __init__(self, default=None, secondary_attribute='', unit=None, - shape=None): - + def __init__(self, default=None, secondary_attribute="", unit=None, shape=None): if default is None and unit is None: - raise ValueError('Either a default quantity value must be ' - 'provided, or a unit must be provided to define a ' - 'QuantityAttribute.') + raise ValueError( + "Either a default quantity value must be provided, or a unit must " + "be provided to define a QuantityAttribute." + ) if default is not None and unit is None: unit = default.unit @@ -321,10 +333,15 @@ def convert_input(self, value): if value is None: return None, False - if (not hasattr(value, 'unit') and self.unit != u.dimensionless_unscaled - and np.any(value != 0)): - raise TypeError('Tried to set a QuantityAttribute with ' - 'something that does not have a unit.') + if ( + not hasattr(value, "unit") + and self.unit != u.dimensionless_unscaled + and np.any(value != 0) + ): + raise TypeError( + "Tried to set a QuantityAttribute with " + "something that does not have a unit." + ) oldvalue = value value = u.Quantity(oldvalue, self.unit, copy=False) @@ -335,7 +352,8 @@ def convert_input(self, value): else: raise ValueError( f'The provided value has shape "{value.shape}", but ' - f'should have shape "{self.shape}"') + f'should have shape "{self.shape}"' + ) converted = oldvalue is not value return value, converted @@ -387,10 +405,11 @@ def convert_input(self, value): # we have to do the import here because of some tricky circular deps from .builtin_frames import ITRS - if not hasattr(value, 'transform_to'): - raise ValueError('"{}" was passed into an ' - 'EarthLocationAttribute, but it does not have ' - '"transform_to" method'.format(value)) + if not hasattr(value, "transform_to"): + raise ValueError( + f'"{value}" was passed into an EarthLocationAttribute, but it does' + ' not have "transform_to" method' + ) itrsobj = value.transform_to(ITRS()) return itrsobj.earth_location, True @@ -415,7 +434,7 @@ class CoordinateAttribute(Attribute): ``default is None`` and no value was supplied during initialization. """ - def __init__(self, frame, default=None, secondary_attribute=''): + def __init__(self, frame, default=None, secondary_attribute=""): self._frame = frame super().__init__(default, secondary_attribute) @@ -472,9 +491,7 @@ class DifferentialAttribute(Attribute): ``default is None`` and no value was supplied during initialization. """ - def __init__(self, default=None, allowed_classes=None, - secondary_attribute=''): - + def __init__(self, default=None, allowed_classes=None, secondary_attribute=""): if allowed_classes is not None: self.allowed_classes = tuple(allowed_classes) else: @@ -511,11 +528,11 @@ def convert_input(self, value): if len(self.allowed_classes) == 1: value = self.allowed_classes[0](value) else: - raise TypeError('Tried to set a DifferentialAttribute with ' - 'an unsupported Differential type {}. Allowed ' - 'classes are: {}' - .format(value.__class__, - self.allowed_classes)) + raise TypeError( + "Tried to set a DifferentialAttribute with an unsupported" + f" Differential type {value.__class__}. Allowed classes are:" + f" {self.allowed_classes}" + ) return value, True diff --git a/astropy/coordinates/baseframe.py b/astropy/coordinates/baseframe.py index 2c0bdfc17b3..15d441af4da 100644 --- a/astropy/coordinates/baseframe.py +++ b/astropy/coordinates/baseframe.py @@ -26,8 +26,12 @@ from .attributes import Attribute from .transformations import TransformGraph -__all__ = ['BaseCoordinateFrame', 'frame_transform_graph', - 'GenericFrame', 'RepresentationMapping'] +__all__ = [ + "BaseCoordinateFrame", + "frame_transform_graph", + "GenericFrame", + "RepresentationMapping", +] # the graph used for all transformations between frames @@ -41,12 +45,11 @@ def _get_repr_cls(value): if value in r.REPRESENTATION_CLASSES: value = r.REPRESENTATION_CLASSES[value] - elif (not isinstance(value, type) or - not issubclass(value, r.BaseRepresentation)): + elif not isinstance(value, type) or not issubclass(value, r.BaseRepresentation): raise ValueError( - 'Representation is {!r} but must be a BaseRepresentation class ' - 'or one of the string aliases {}'.format( - value, list(r.REPRESENTATION_CLASSES))) + f"Representation is {value!r} but must be a BaseRepresentation class " + f"or one of the string aliases {list(r.REPRESENTATION_CLASSES)}" + ) return value @@ -60,12 +63,11 @@ def _get_diff_cls(value): if value in r.DIFFERENTIAL_CLASSES: value = r.DIFFERENTIAL_CLASSES[value] - elif (not isinstance(value, type) or - not issubclass(value, r.BaseDifferential)): + elif not isinstance(value, type) or not issubclass(value, r.BaseDifferential): raise ValueError( - 'Differential is {!r} but must be a BaseDifferential class ' - 'or one of the string aliases {}'.format( - value, list(r.DIFFERENTIAL_CLASSES))) + f"Differential is {value!r} but must be a BaseDifferential class " + f"or one of the string aliases {list(r.DIFFERENTIAL_CLASSES)}" + ) return value @@ -89,30 +91,31 @@ class for the representation of the base coordinates. If a string, ``diffferentials``. """ base = _get_repr_cls(base) - repr_classes = {'base': base} + repr_classes = {"base": base} for name, differential_type in differentials.items(): - if differential_type == 'base': + if differential_type == "base": # We don't want to fail for this case. differential_type = r.DIFFERENTIAL_CLASSES.get(base.get_name(), None) elif differential_type in r.DIFFERENTIAL_CLASSES: differential_type = r.DIFFERENTIAL_CLASSES[differential_type] - elif (differential_type is not None - and (not isinstance(differential_type, type) - or not issubclass(differential_type, r.BaseDifferential))): + elif differential_type is not None and ( + not isinstance(differential_type, type) + or not issubclass(differential_type, r.BaseDifferential) + ): raise ValueError( - 'Differential is {!r} but must be a BaseDifferential class ' - 'or one of the string aliases {}'.format( - differential_type, list(r.DIFFERENTIAL_CLASSES))) + "Differential is {differential_type!r} but must be a BaseDifferential" + f" class or one of the string aliases {list(r.DIFFERENTIAL_CLASSES)}" + ) repr_classes[name] = differential_type return repr_classes -_RepresentationMappingBase = \ - namedtuple('RepresentationMapping', - ('reprname', 'framename', 'defaultunit')) +_RepresentationMappingBase = namedtuple( + "RepresentationMapping", ("reprname", "framename", "defaultunit") +) class RepresentationMapping(_RepresentationMappingBase): @@ -126,7 +129,7 @@ class RepresentationMapping(_RepresentationMappingBase): should be done). """ - def __new__(cls, reprname, framename, defaultunit='recommended'): + def __new__(cls, reprname, framename, defaultunit="recommended"): # this trick just provides some defaults return super().__new__(cls, reprname, framename, defaultunit) @@ -220,22 +223,21 @@ class BaseCoordinateFrame(ShapedLikeNDArray): # Default empty frame_attributes dict def __init_subclass__(cls, **kwargs): - # We first check for explicitly set values for these: - default_repr = getattr(cls, 'default_representation', None) - default_diff = getattr(cls, 'default_differential', None) - repr_info = getattr(cls, 'frame_specific_representation_info', None) + default_repr = getattr(cls, "default_representation", None) + default_diff = getattr(cls, "default_differential", None) + repr_info = getattr(cls, "frame_specific_representation_info", None) # Then, to make sure this works for subclasses-of-subclasses, we also # have to check for cases where the attribute names have already been # replaced by underscore-prefaced equivalents by the logic below: if default_repr is None or isinstance(default_repr, property): - default_repr = getattr(cls, '_default_representation', None) + default_repr = getattr(cls, "_default_representation", None) if default_diff is None or isinstance(default_diff, property): - default_diff = getattr(cls, '_default_differential', None) + default_diff = getattr(cls, "_default_differential", None) if repr_info is None or isinstance(repr_info, property): - repr_info = getattr(cls, '_frame_specific_representation_info', None) + repr_info = getattr(cls, "_frame_specific_representation_info", None) repr_info = cls._infer_repr_info(repr_info) @@ -243,14 +245,21 @@ def __init_subclass__(cls, **kwargs): # be read-only to make them immutable after creation. # We copy attributes instead of linking to make sure there's no # accidental cross-talk between classes - cls._create_readonly_property('default_representation', default_repr, - 'Default representation for position data') - cls._create_readonly_property('default_differential', default_diff, - 'Default representation for differential data ' - '(e.g., velocity)') - cls._create_readonly_property('frame_specific_representation_info', - copy.deepcopy(repr_info), - 'Mapping for frame-specific component names') + cls._create_readonly_property( + "default_representation", + default_repr, + "Default representation for position data", + ) + cls._create_readonly_property( + "default_differential", + default_diff, + "Default representation for differential data (e.g., velocity)", + ) + cls._create_readonly_property( + "frame_specific_representation_info", + copy.deepcopy(repr_info), + "Mapping for frame-specific component names", + ) # Set the frame attributes. We first construct the attributes from # superclasses, going in reverse order to keep insertion order, @@ -273,11 +282,11 @@ def __init_subclass__(cls, **kwargs): cls.frame_attributes = frame_attrs # Deal with setting the name of the frame: - if not hasattr(cls, 'name'): + if not hasattr(cls, "name"): cls.name = cls.__name__.lower() - elif (BaseCoordinateFrame not in cls.__bases__ and - cls.name in [getattr(base, 'name', None) - for base in cls.__bases__]): + elif BaseCoordinateFrame not in cls.__bases__ and cls.name in [ + getattr(base, "name", None) for base in cls.__bases__ + ]: # This may be a subclass of a subclass of BaseCoordinateFrame, # like ICRS(BaseRADecFrame). In this case, cls.name will have been # set by init_subclass @@ -294,11 +303,19 @@ def __init_subclass__(cls, **kwargs): # (via FrameAttribute.__get__/convert_input) cls.get_frame_attr_defaults() - def __init__(self, *args, copy=True, representation_type=None, - differential_type=None, **kwargs): + def __init__( + self, + *args, + copy=True, + representation_type=None, + differential_type=None, + **kwargs, + ): self._attr_names_with_defaults = [] - self._representation = self._infer_representation(representation_type, differential_type) + self._representation = self._infer_representation( + representation_type, differential_type + ) self._data = self._infer_data(args, copy, kwargs) # possibly None. # Set frame attributes, if any @@ -311,18 +328,19 @@ def __init__(self, *args, copy=True, representation_type=None, if fnm in kwargs: value = kwargs.pop(fnm) - setattr(self, '_' + fnm, value) + setattr(self, "_" + fnm, value) # Validate attribute by getting it. If the instance has data, # this also checks its shape is OK. If not, we do it below. values[fnm] = getattr(self, fnm) else: - setattr(self, '_' + fnm, fdefault) + setattr(self, "_" + fnm, fdefault) self._attr_names_with_defaults.append(fnm) if kwargs: raise TypeError( - f'Coordinate frame {self.__class__.__name__} got unexpected ' - f'keywords: {list(kwargs)}') + f"Coordinate frame {self.__class__.__name__} got unexpected " + f"keywords: {list(kwargs)}" + ) # We do ``is None`` because self._data might evaluate to false for # empty arrays or data == 0 @@ -330,15 +348,19 @@ def __init__(self, *args, copy=True, representation_type=None, # No data: we still need to check that any non-scalar attributes # have consistent shapes. Collect them for all attributes with # size > 1 (which should be array-like and thus have a shape). - shapes = {fnm: value.shape for fnm, value in values.items() - if getattr(value, 'shape', ())} + shapes = { + fnm: value.shape + for fnm, value in values.items() + if getattr(value, "shape", ()) + } if shapes: if len(shapes) > 1: try: self._no_data_shape = check_broadcast(*shapes.values()) except ValueError as err: raise ValueError( - f"non-scalar attributes with inconsistent shapes: {shapes}") from err + f"non-scalar attributes with inconsistent shapes: {shapes}" + ) from err # Above, we checked that it is possible to broadcast all # shapes. By getting and thus validating the attributes, @@ -356,41 +378,44 @@ def __init__(self, *args, copy=True, representation_type=None, # This makes the cache keys backwards-compatible, but also adds # support for having differentials attached to the frame data # representation object. - if 's' in self._data.differentials: + if "s" in self._data.differentials: # TODO: assumes a velocity unit differential - key = (self._data.__class__.__name__, - self._data.differentials['s'].__class__.__name__, - False) + key = ( + self._data.__class__.__name__, + self._data.differentials["s"].__class__.__name__, + False, + ) else: key = (self._data.__class__.__name__, False) # Set up representation cache. - self.cache['representation'][key] = self._data + self.cache["representation"][key] = self._data def _infer_representation(self, representation_type, differential_type): if representation_type is None and differential_type is None: - return {'base': self.default_representation, 's': self.default_differential} + return {"base": self.default_representation, "s": self.default_differential} if representation_type is None: representation_type = self.default_representation - if (inspect.isclass(differential_type) - and issubclass(differential_type, r.BaseDifferential)): + if inspect.isclass(differential_type) and issubclass( + differential_type, r.BaseDifferential + ): # TODO: assumes the differential class is for the velocity # differential - differential_type = {'s': differential_type} + differential_type = {"s": differential_type} elif isinstance(differential_type, str): # TODO: assumes the differential class is for the velocity # differential diff_cls = r.DIFFERENTIAL_CLASSES[differential_type] - differential_type = {'s': diff_cls} + differential_type = {"s": diff_cls} elif differential_type is None: if representation_type == self.default_representation: - differential_type = {'s': self.default_differential} + differential_type = {"s": self.default_differential} else: - differential_type = {'s': 'base'} # see set_representation_cls() + differential_type = {"s": "base"} # see set_representation_cls() return _get_repr_classes(representation_type, **differential_type) @@ -400,24 +425,25 @@ def _infer_data(self, args, copy, kwargs): differential_data = None args = list(args) # need to be able to pop them - if (len(args) > 0) and (isinstance(args[0], r.BaseRepresentation) or - args[0] is None): + if args and (isinstance(args[0], r.BaseRepresentation) or args[0] is None): representation_data = args.pop(0) # This can still be None if len(args) > 0: raise TypeError( - 'Cannot create a frame with both a representation object ' - 'and other positional arguments') + "Cannot create a frame with both a representation object " + "and other positional arguments" + ) if representation_data is not None: diffs = representation_data.differentials - differential_data = diffs.get('s', None) - if ((differential_data is None and len(diffs) > 0) or - (differential_data is not None and len(diffs) > 1)): - raise ValueError('Multiple differentials are associated ' - 'with the representation object passed in ' - 'to the frame initializer. Only a single ' - 'velocity differential is supported. Got: ' - '{}'.format(diffs)) + differential_data = diffs.get("s", None) + if (differential_data is None and len(diffs) > 0) or ( + differential_data is not None and len(diffs) > 1 + ): + raise ValueError( + "Multiple differentials are associated with the representation" + " object passed in to the frame initializer. Only a single" + f" velocity differential is supported. Got: {diffs}" + ) else: representation_cls = self.get_representation_cls() @@ -438,41 +464,40 @@ def _infer_data(self, args, copy, kwargs): # currently removing it has a performance regression for # unitspherical because of the try-related overhead. # Also frames have no way to indicate what the "distance" is - if repr_kwargs.get('distance', True) is None: - del repr_kwargs['distance'] + if repr_kwargs.get("distance", True) is None: + del repr_kwargs["distance"] - if (issubclass(representation_cls, - r.SphericalRepresentation) - and 'distance' not in repr_kwargs): + if ( + issubclass(representation_cls, r.SphericalRepresentation) + and "distance" not in repr_kwargs + ): representation_cls = representation_cls._unit_representation try: - representation_data = representation_cls(copy=copy, - **repr_kwargs) + representation_data = representation_cls(copy=copy, **repr_kwargs) except TypeError as e: # this except clause is here to make the names of the # attributes more human-readable. Without this the names # come from the representation instead of the frame's # attribute names. try: - representation_data = ( - representation_cls._unit_representation( - copy=copy, **repr_kwargs)) + representation_data = representation_cls._unit_representation( + copy=copy, **repr_kwargs + ) except Exception: msg = str(e) names = self.get_representation_component_names() for frame_name, repr_name in names.items(): msg = msg.replace(repr_name, frame_name) - msg = msg.replace('__init__()', - f'{self.__class__.__name__}()') + msg = msg.replace("__init__()", f"{self.__class__.__name__}()") e.args = (msg,) raise e # Now we handle the Differential data: # Get any differential data passed in to the frame initializer # using keyword or positional arguments for the component names - differential_cls = self.get_representation_cls('s') - diff_component_names = self.get_representation_component_names('s') + differential_cls = self.get_representation_cls("s") + diff_component_names = self.get_representation_component_names("s") diff_kwargs = {} for nmkw, nmrep in diff_component_names.items(): if len(args) > 0: @@ -482,38 +507,42 @@ def _infer_data(self, args, copy, kwargs): diff_kwargs[nmrep] = kwargs.pop(nmkw) if diff_kwargs: - if (hasattr(differential_cls, '_unit_differential') - and 'd_distance' not in diff_kwargs): + if ( + hasattr(differential_cls, "_unit_differential") + and "d_distance" not in diff_kwargs + ): differential_cls = differential_cls._unit_differential - elif len(diff_kwargs) == 1 and 'd_distance' in diff_kwargs: + elif len(diff_kwargs) == 1 and "d_distance" in diff_kwargs: differential_cls = r.RadialDifferential try: - differential_data = differential_cls(copy=copy, - **diff_kwargs) + differential_data = differential_cls(copy=copy, **diff_kwargs) except TypeError as e: # this except clause is here to make the names of the # attributes more human-readable. Without this the names # come from the representation instead of the frame's # attribute names. msg = str(e) - names = self.get_representation_component_names('s') + names = self.get_representation_component_names("s") for frame_name, repr_name in names.items(): msg = msg.replace(repr_name, frame_name) - msg = msg.replace('__init__()', - f'{self.__class__.__name__}()') + msg = msg.replace("__init__()", f"{self.__class__.__name__}()") e.args = (msg,) raise if len(args) > 0: raise TypeError( - '{}.__init__ had {} remaining unhandled arguments'.format( - self.__class__.__name__, len(args))) + "{}.__init__ had {} remaining unhandled arguments".format( + self.__class__.__name__, len(args) + ) + ) if representation_data is None and differential_data is not None: - raise ValueError("Cannot pass in differential component data " - "without positional (representation) data.") + raise ValueError( + "Cannot pass in differential component data " + "without positional (representation) data." + ) if differential_data: # Check that differential data provided has units compatible @@ -521,27 +550,36 @@ def _infer_data(self, args, copy, kwargs): # NOTE: there is no dimensionless time while lengths can be # dimensionless (u.dimensionless_unscaled). for comp in representation_data.components: - if (diff_comp := f'd_{comp}') in differential_data.components: + if (diff_comp := f"d_{comp}") in differential_data.components: current_repr_unit = representation_data._units[comp] current_diff_unit = differential_data._units[diff_comp] expected_unit = current_repr_unit / u.s if not current_diff_unit.is_equivalent(expected_unit): - for key, val in self.get_representation_component_names().items(): + for ( + key, + val, + ) in self.get_representation_component_names().items(): if val == comp: current_repr_name = key break - for key, val in self.get_representation_component_names('s').items(): + for key, val in self.get_representation_component_names( + "s" + ).items(): if val == diff_comp: current_diff_name = key break raise ValueError( - f'{current_repr_name} has unit "{current_repr_unit}" with physical ' - f'type "{current_repr_unit.physical_type}", but {current_diff_name} ' - f'has incompatible unit "{current_diff_unit}" with physical type ' - f'"{current_diff_unit.physical_type}" instead of the expected ' - f'"{(expected_unit).physical_type}".') - - representation_data = representation_data.with_differentials({'s': differential_data}) + f'{current_repr_name} has unit "{current_repr_unit}" with' + f' physical type "{current_repr_unit.physical_type}", but' + f" {current_diff_name} has incompatible unit" + f' "{current_diff_unit}" with physical type' + f' "{current_diff_unit.physical_type}" instead of the' + f' expected "{(expected_unit).physical_type}".' + ) + + representation_data = representation_data.with_differentials( + {"s": differential_data} + ) return representation_data @@ -574,59 +612,73 @@ def _infer_repr_info(cls, repr_info): repr_info[_cls] = repr_info.pop(cls_or_name) # The default spherical names are 'lon' and 'lat' - repr_info.setdefault(r.SphericalRepresentation, - [RepresentationMapping('lon', 'lon'), - RepresentationMapping('lat', 'lat')]) - - sph_component_map = {m.reprname: m.framename - for m in repr_info[r.SphericalRepresentation]} - - repr_info.setdefault(r.SphericalCosLatDifferential, [ - RepresentationMapping( - 'd_lon_coslat', - 'pm_{lon}_cos{lat}'.format(**sph_component_map), - u.mas/u.yr), - RepresentationMapping('d_lat', - 'pm_{lat}'.format(**sph_component_map), - u.mas/u.yr), - RepresentationMapping('d_distance', 'radial_velocity', - u.km/u.s) - ]) - - repr_info.setdefault(r.SphericalDifferential, [ - RepresentationMapping('d_lon', - 'pm_{lon}'.format(**sph_component_map), - u.mas/u.yr), - RepresentationMapping('d_lat', - 'pm_{lat}'.format(**sph_component_map), - u.mas/u.yr), - RepresentationMapping('d_distance', 'radial_velocity', - u.km/u.s) - ]) - - repr_info.setdefault(r.CartesianDifferential, [ - RepresentationMapping('d_x', 'v_x', u.km/u.s), - RepresentationMapping('d_y', 'v_y', u.km/u.s), - RepresentationMapping('d_z', 'v_z', u.km/u.s)]) + repr_info.setdefault( + r.SphericalRepresentation, + [RepresentationMapping("lon", "lon"), RepresentationMapping("lat", "lat")], + ) + + sph_component_map = { + m.reprname: m.framename for m in repr_info[r.SphericalRepresentation] + } + + repr_info.setdefault( + r.SphericalCosLatDifferential, + [ + RepresentationMapping( + "d_lon_coslat", + "pm_{lon}_cos{lat}".format(**sph_component_map), + u.mas / u.yr, + ), + RepresentationMapping( + "d_lat", "pm_{lat}".format(**sph_component_map), u.mas / u.yr + ), + RepresentationMapping("d_distance", "radial_velocity", u.km / u.s), + ], + ) + + repr_info.setdefault( + r.SphericalDifferential, + [ + RepresentationMapping( + "d_lon", "pm_{lon}".format(**sph_component_map), u.mas / u.yr + ), + RepresentationMapping( + "d_lat", "pm_{lat}".format(**sph_component_map), u.mas / u.yr + ), + RepresentationMapping("d_distance", "radial_velocity", u.km / u.s), + ], + ) + + repr_info.setdefault( + r.CartesianDifferential, + [ + RepresentationMapping("d_x", "v_x", u.km / u.s), + RepresentationMapping("d_y", "v_y", u.km / u.s), + RepresentationMapping("d_z", "v_z", u.km / u.s), + ], + ) # Unit* classes should follow the same naming conventions # TODO: this adds some unnecessary mappings for the Unit classes, so # this could be cleaned up, but in practice doesn't seem to have any # negative side effects - repr_info.setdefault(r.UnitSphericalRepresentation, - repr_info[r.SphericalRepresentation]) + repr_info.setdefault( + r.UnitSphericalRepresentation, repr_info[r.SphericalRepresentation] + ) - repr_info.setdefault(r.UnitSphericalCosLatDifferential, - repr_info[r.SphericalCosLatDifferential]) + repr_info.setdefault( + r.UnitSphericalCosLatDifferential, repr_info[r.SphericalCosLatDifferential] + ) - repr_info.setdefault(r.UnitSphericalDifferential, - repr_info[r.SphericalDifferential]) + repr_info.setdefault( + r.UnitSphericalDifferential, repr_info[r.SphericalDifferential] + ) return repr_info @classmethod def _create_readonly_property(cls, attr_name, value, doc=None): - private_attr = '_' + attr_name + private_attr = "_" + attr_name def getter(self): return getattr(self, private_attr) @@ -664,8 +716,9 @@ def data(self): check if data is present on this frame object. """ if self._data is None: - raise ValueError('The frame object "{!r}" does not have ' - 'associated data'.format(self)) + raise ValueError( + f'The frame object "{self!r}" does not have associated data' + ) return self._data @property @@ -698,8 +751,7 @@ def isscalar(self): @classmethod def get_frame_attr_defaults(cls): """Return a dict with the defaults for each frame attribute""" - return {name: getattr(cls, name) - for name in cls.frame_attributes} + return {name: getattr(cls, name) for name in cls.frame_attributes} @deprecated( "5.2", @@ -709,14 +761,14 @@ def get_frame_attr_defaults(cls): " version. Use {alternative}() to obtain a dict of frame attribute names" " and default values." " The fastest way to obtain the names is frame_attributes.keys()" - ) + ), ) @classmethod def get_frame_attr_names(cls): """Return a dict with the defaults for each frame attribute""" return cls.get_frame_attr_defaults() - def get_representation_cls(self, which='base'): + def get_representation_cls(self, which="base"): """The class used for part of this frame's data. Parameters @@ -736,7 +788,7 @@ def get_representation_cls(self, which='base'): else: return self._representation - def set_representation_cls(self, base=None, s='base'): + def set_representation_cls(self, base=None, s="base"): """Set representation and/or differential class for this frame's data. Parameters @@ -750,18 +802,20 @@ def set_representation_cls(self, base=None, s='base'): If `None`, the representation will drop any differentials. """ if base is None: - base = self._representation['base'] + base = self._representation["base"] self._representation = _get_repr_classes(base=base, s=s) representation_type = property( - fget=get_representation_cls, fset=set_representation_cls, + fget=get_representation_cls, + fset=set_representation_cls, doc="""The representation class used for this frame's data. This will be a subclass from `~astropy.coordinates.BaseRepresentation`. Can also be *set* using the string name of the representation. If you wish to set an explicit differential class (rather than have it be inferred), use the ``set_representation_cls`` method. - """) + """, + ) @property def differential_type(self): @@ -772,7 +826,7 @@ def differential_type(self): For simultaneous setting of representation and differentials, see the ``set_representation_cls`` method. """ - return self.get_representation_cls('s') + return self.get_representation_cls("s") @differential_type.setter def differential_type(self, value): @@ -786,23 +840,29 @@ def _get_representation_info(cls): # note that if so moved, the cache should be acceessed as # self.__class__._frame_class_cache - if cls._frame_class_cache.get('last_reprdiff_hash', None) != r.get_reprdiff_cls_hash(): + if ( + cls._frame_class_cache.get("last_reprdiff_hash", None) + != r.get_reprdiff_cls_hash() + ): repr_attrs = {} - for repr_diff_cls in (list(r.REPRESENTATION_CLASSES.values()) + - list(r.DIFFERENTIAL_CLASSES.values())): - repr_attrs[repr_diff_cls] = {'names': [], 'units': []} + for repr_diff_cls in list(r.REPRESENTATION_CLASSES.values()) + list( + r.DIFFERENTIAL_CLASSES.values() + ): + repr_attrs[repr_diff_cls] = {"names": [], "units": []} for c, c_cls in repr_diff_cls.attr_classes.items(): - repr_attrs[repr_diff_cls]['names'].append(c) + repr_attrs[repr_diff_cls]["names"].append(c) rec_unit = u.deg if issubclass(c_cls, Angle) else None - repr_attrs[repr_diff_cls]['units'].append(rec_unit) - - for repr_diff_cls, mappings in cls._frame_specific_representation_info.items(): + repr_attrs[repr_diff_cls]["units"].append(rec_unit) + for ( + repr_diff_cls, + mappings, + ) in cls._frame_specific_representation_info.items(): # take the 'names' and 'units' tuples from repr_attrs, # and then use the RepresentationMapping objects # to update as needed for this frame. - nms = repr_attrs[repr_diff_cls]['names'] - uns = repr_attrs[repr_diff_cls]['units'] + nms = repr_attrs[repr_diff_cls]["names"] + uns = repr_attrs[repr_diff_cls]["units"] comptomap = {m.reprname: m for m in mappings} for i, c in enumerate(repr_diff_cls.attr_classes.keys()): if c in comptomap: @@ -811,18 +871,20 @@ def _get_representation_info(cls): # need the isinstance because otherwise if it's a unit it # will try to compare to the unit string representation - if not (isinstance(mapp.defaultunit, str) - and mapp.defaultunit == 'recommended'): + if not ( + isinstance(mapp.defaultunit, str) + and mapp.defaultunit == "recommended" + ): uns[i] = mapp.defaultunit # else we just leave it as recommended_units says above # Convert to tuples so that this can't mess with frame internals - repr_attrs[repr_diff_cls]['names'] = tuple(nms) - repr_attrs[repr_diff_cls]['units'] = tuple(uns) + repr_attrs[repr_diff_cls]["names"] = tuple(nms) + repr_attrs[repr_diff_cls]["units"] = tuple(uns) - cls._frame_class_cache['representation_info'] = repr_attrs - cls._frame_class_cache['last_reprdiff_hash'] = r.get_reprdiff_cls_hash() - return cls._frame_class_cache['representation_info'] + cls._frame_class_cache["representation_info"] = repr_attrs + cls._frame_class_cache["last_reprdiff_hash"] = r.get_reprdiff_cls_hash() + return cls._frame_class_cache["representation_info"] @lazyproperty def representation_info(self): @@ -832,25 +894,25 @@ def representation_info(self): """ return self._get_representation_info() - def get_representation_component_names(self, which='base'): + def get_representation_component_names(self, which="base"): out = {} repr_or_diff_cls = self.get_representation_cls(which) if repr_or_diff_cls is None: return out data_names = repr_or_diff_cls.attr_classes.keys() - repr_names = self.representation_info[repr_or_diff_cls]['names'] + repr_names = self.representation_info[repr_or_diff_cls]["names"] for repr_name, data_name in zip(repr_names, data_names): out[repr_name] = data_name return out - def get_representation_component_units(self, which='base'): + def get_representation_component_units(self, which="base"): out = {} repr_or_diff_cls = self.get_representation_cls(which) if repr_or_diff_cls is None: return out repr_attrs = self.representation_info[repr_or_diff_cls] - repr_names = repr_attrs['names'] - repr_units = repr_attrs['units'] + repr_names = repr_attrs["names"] + repr_units = repr_attrs["units"] for repr_name, repr_unit in zip(repr_names, repr_units): if repr_unit: out[repr_name] = repr_unit @@ -881,17 +943,17 @@ def _replicate(self, data, copy=False, **kwargs): # to use frame_obj.representation instead of frame_obj.data to get the # underlying representation object [e.g., #2890] if inspect.isclass(data): - raise TypeError('Class passed as data instead of a representation ' - 'instance. If you called frame.representation, this' - ' returns the representation class. frame.data ' - 'returns the instantiated object - you may want to ' - ' use this instead.') + raise TypeError( + "Class passed as data instead of a representation instance. If you" + " called frame.representation, this returns the representation class." + " frame.data returns the instantiated object - you may want to use" + " this instead." + ) if copy and data is not None: data = data.copy() for attr in self.frame_attributes: - if (attr not in self._attr_names_with_defaults - and attr not in kwargs): + if attr not in self._attr_names_with_defaults and attr not in kwargs: value = getattr(self, attr) if copy: value = value.copy() @@ -981,7 +1043,7 @@ def realize_frame(self, data, **kwargs): """ return self._replicate(data, **kwargs) - def represent_as(self, base, s='base', in_frame_units=False): + def represent_as(self, base, s="base", in_frame_units=False): """ Generate and return a new representation of this frame's `data` as a Representation object. @@ -1036,63 +1098,75 @@ def represent_as(self, base, s='base', in_frame_units=False): # it is, we ignore the value of `new_differential` and warn about the # position change if isinstance(s, bool): - warnings.warn("The argument position for `in_frame_units` in " - "`represent_as` has changed. Use as a keyword " - "argument if needed.", AstropyWarning) + warnings.warn( + "The argument position for `in_frame_units` in `represent_as` has" + " changed. Use as a keyword argument if needed.", + AstropyWarning, + ) in_frame_units = s - s = 'base' + s = "base" # In the future, we may want to support more differentials, in which # case one probably needs to define **kwargs above and use it here. # But for now, we only care about the velocity. repr_classes = _get_repr_classes(base=base, s=s) - representation_cls = repr_classes['base'] + representation_cls = repr_classes["base"] # We only keep velocity information - if 's' in self.data.differentials: + if "s" in self.data.differentials: # For the default 'base' option in which _get_repr_classes has # given us a best guess based on the representation class, we only # use it if the class we had already is incompatible. - if (s == 'base' - and (self.data.differentials['s'].__class__ - in representation_cls._compatible_differentials)): - differential_cls = self.data.differentials['s'].__class__ + if s == "base" and ( + self.data.differentials["s"].__class__ + in representation_cls._compatible_differentials + ): + differential_cls = self.data.differentials["s"].__class__ else: - differential_cls = repr_classes['s'] - elif s is None or s == 'base': + differential_cls = repr_classes["s"] + elif s is None or s == "base": differential_cls = None else: - raise TypeError('Frame data has no associated differentials ' - '(i.e. the frame has no velocity data) - ' - 'represent_as() only accepts a new ' - 'representation.') + raise TypeError( + "Frame data has no associated differentials (i.e. the frame has no" + " velocity data) - represent_as() only accepts a new representation." + ) if differential_cls: - cache_key = (representation_cls.__name__, - differential_cls.__name__, in_frame_units) + cache_key = ( + representation_cls.__name__, + differential_cls.__name__, + in_frame_units, + ) else: cache_key = (representation_cls.__name__, in_frame_units) - cached_repr = self.cache['representation'].get(cache_key) + cached_repr = self.cache["representation"].get(cache_key) if not cached_repr: if differential_cls: # Sanity check to ensure we do not just drop radial # velocity. TODO: should Representation.represent_as # allow this transformation in the first place? - if (isinstance(self.data, r.UnitSphericalRepresentation) + if ( + isinstance(self.data, r.UnitSphericalRepresentation) and issubclass(representation_cls, r.CartesianRepresentation) - and not isinstance(self.data.differentials['s'], - (r.UnitSphericalDifferential, - r.UnitSphericalCosLatDifferential, - r.RadialDifferential))): + and not isinstance( + self.data.differentials["s"], + ( + r.UnitSphericalDifferential, + r.UnitSphericalCosLatDifferential, + r.RadialDifferential, + ), + ) + ): raise u.UnitConversionError( - 'need a distance to retrieve a cartesian representation ' - 'when both radial velocity and proper motion are present, ' - 'since otherwise the units cannot match.') + "need a distance to retrieve a cartesian representation " + "when both radial velocity and proper motion are present, " + "since otherwise the units cannot match." + ) # TODO NOTE: only supports a single differential - data = self.data.represent_as(representation_cls, - differential_cls) - diff = data.differentials['s'] # TODO: assumes velocity + data = self.data.represent_as(representation_cls, differential_cls) + diff = data.differentials["s"] # TODO: assumes velocity else: data = self.data.represent_as(representation_cls) @@ -1101,36 +1175,43 @@ def represent_as(self, base, s='base', in_frame_units=False): new_attrs = self.representation_info.get(representation_cls) if new_attrs and in_frame_units: datakwargs = {comp: getattr(data, comp) for comp in data.components} - for comp, new_attr_unit in zip(data.components, new_attrs['units']): + for comp, new_attr_unit in zip(data.components, new_attrs["units"]): if new_attr_unit: datakwargs[comp] = datakwargs[comp].to(new_attr_unit) data = data.__class__(copy=False, **datakwargs) if differential_cls: # the original differential - data_diff = self.data.differentials['s'] + data_diff = self.data.differentials["s"] # If the new differential is known to this frame and has a # defined set of names and units, then use that. new_attrs = self.representation_info.get(differential_cls) if new_attrs and in_frame_units: diffkwargs = {comp: getattr(diff, comp) for comp in diff.components} - for comp, new_attr_unit in zip(diff.components, - new_attrs['units']): + for comp, new_attr_unit in zip(diff.components, new_attrs["units"]): # Some special-casing to treat a situation where the # input data has a UnitSphericalDifferential or a # RadialDifferential. It is re-represented to the # frame's differential class (which might be, e.g., a # dimensional Differential), so we don't want to try to # convert the empty component units - if (isinstance(data_diff, - (r.UnitSphericalDifferential, - r.UnitSphericalCosLatDifferential)) - and comp not in data_diff.__class__.attr_classes): + if ( + isinstance( + data_diff, + ( + r.UnitSphericalDifferential, + r.UnitSphericalCosLatDifferential, + ), + ) + and comp not in data_diff.__class__.attr_classes + ): continue - elif (isinstance(data_diff, r.RadialDifferential) - and comp not in data_diff.__class__.attr_classes): + elif ( + isinstance(data_diff, r.RadialDifferential) + and comp not in data_diff.__class__.attr_classes + ): continue # Try to convert to requested units. Since that might @@ -1155,11 +1236,11 @@ def represent_as(self, base, s='base', in_frame_units=False): # with strange units for the d_lon and d_lat attributes. # This then causes the dictionary key check to fail (i.e. # comparison against `diff._get_deriv_key()`) - data._differentials.update({'s': diff}) + data._differentials.update({"s": diff}) - self.cache['representation'][cache_key] = data + self.cache["representation"][cache_key] = data - return self.cache['representation'][cache_key] + return self.cache["representation"][cache_key] def transform_to(self, new_frame): """ @@ -1185,39 +1266,44 @@ def transform_to(self, new_frame): from .errors import ConvertError if self._data is None: - raise ValueError('Cannot transform a frame with no data') - - if (getattr(self.data, 'differentials', None) - and hasattr(self, 'obstime') and hasattr(new_frame, 'obstime') - and np.any(self.obstime != new_frame.obstime)): - raise NotImplementedError('You cannot transform a frame that has ' - 'velocities to another frame at a ' - 'different obstime. If you think this ' - 'should (or should not) be possible, ' - 'please comment at https://github.com/astropy/astropy/issues/6280') + raise ValueError("Cannot transform a frame with no data") + + if ( + getattr(self.data, "differentials", None) + and hasattr(self, "obstime") + and hasattr(new_frame, "obstime") + and np.any(self.obstime != new_frame.obstime) + ): + raise NotImplementedError( + "You cannot transform a frame that has velocities to another frame at a" + " different obstime. If you think this should (or should not) be" + " possible, please comment at" + " https://github.com/astropy/astropy/issues/6280" + ) if inspect.isclass(new_frame): - warnings.warn("Transforming a frame instance to a frame class (as opposed to another " - "frame instance) will not be supported in the future. Either " - "explicitly instantiate the target frame, or first convert the source " - "frame instance to a `astropy.coordinates.SkyCoord` and use its " - "`transform_to()` method.", - AstropyDeprecationWarning) + warnings.warn( + "Transforming a frame instance to a frame class (as opposed to another " + "frame instance) will not be supported in the future. Either " + "explicitly instantiate the target frame, or first convert the source " + "frame instance to a `astropy.coordinates.SkyCoord` and use its " + "`transform_to()` method.", + AstropyDeprecationWarning, + ) # Use the default frame attributes for this class new_frame = new_frame() - if hasattr(new_frame, '_sky_coord_frame'): + if hasattr(new_frame, "_sky_coord_frame"): # Input new_frame is not a frame instance or class and is most # likely a SkyCoord object. new_frame = new_frame._sky_coord_frame - trans = frame_transform_graph.get_transform(self.__class__, - new_frame.__class__) + trans = frame_transform_graph.get_transform(self.__class__, new_frame.__class__) if trans is None: if new_frame is self.__class__: # no special transform needed, but should update frame info return new_frame.realize_frame(self.data) - msg = 'Cannot transform from {0} to {1}' + msg = "Cannot transform from {0} to {1}" raise ConvertError(msg.format(self.__class__, new_frame.__class__)) return trans(self, new_frame) @@ -1258,7 +1344,7 @@ def is_transformable_to(self, new_frame): if trans is None: if new_frame_cls is self.__class__: - return 'same' + return "same" else: return False else: @@ -1310,24 +1396,30 @@ def _frameattr_equiv(left_fattr, right_fattr): right_is_repr = isinstance(right_fattr, r.BaseRepresentationOrDifferential) if left_is_repr and right_is_repr: # both are representations. - if (getattr(left_fattr, 'differentials', False) or - getattr(right_fattr, 'differentials', False)): - warnings.warn('Two representation frame attributes were ' - 'checked for equivalence when at least one of' - ' them has differentials. This yields False ' - 'even if the underlying representations are ' - 'equivalent (although this may change in ' - 'future versions of Astropy)', AstropyWarning) + if getattr(left_fattr, "differentials", False) or getattr( + right_fattr, "differentials", False + ): + warnings.warn( + "Two representation frame attributes were checked for equivalence" + " when at least one of them has differentials. This yields False" + " even if the underlying representations are equivalent (although" + " this may change in future versions of Astropy)", + AstropyWarning, + ) return False if isinstance(right_fattr, left_fattr.__class__): # if same representation type, compare components. - return np.all([(getattr(left_fattr, comp) == - getattr(right_fattr, comp)) - for comp in left_fattr.components]) + return np.all( + [ + (getattr(left_fattr, comp) == getattr(right_fattr, comp)) + for comp in left_fattr.components + ] + ) else: # convert to cartesian and see if they match - return np.all(left_fattr.to_cartesian().xyz == - right_fattr.to_cartesian().xyz) + return np.all( + left_fattr.to_cartesian().xyz == right_fattr.to_cartesian().xyz + ) elif left_is_repr or right_is_repr: return False @@ -1369,13 +1461,15 @@ def is_equivalent_frame(self, other): """ if self.__class__ == other.__class__: for frame_attr_name in self.frame_attributes: - if not self._frameattr_equiv(getattr(self, frame_attr_name), - getattr(other, frame_attr_name)): + if not self._frameattr_equiv( + getattr(self, frame_attr_name), getattr(other, frame_attr_name) + ): return False return True elif not isinstance(other, BaseCoordinateFrame): - raise TypeError("Tried to do is_equivalent_frame on something that " - "isn't a frame") + raise TypeError( + "Tried to do is_equivalent_frame on something that isn't a frame" + ) else: return False @@ -1384,33 +1478,38 @@ def __repr__(self): data_repr = self._data_repr() if frameattrs: - frameattrs = f' ({frameattrs})' + frameattrs = f" ({frameattrs})" if data_repr: - return f'<{self.__class__.__name__} Coordinate{frameattrs}: {data_repr}>' + return f"<{self.__class__.__name__} Coordinate{frameattrs}: {data_repr}>" else: - return f'<{self.__class__.__name__} Frame{frameattrs}>' + return f"<{self.__class__.__name__} Frame{frameattrs}>" def _data_repr(self): """Returns a string representation of the coordinate data.""" if not self.has_data: - return '' + return "" if self.representation_type: - if (hasattr(self.representation_type, '_unit_representation') - and isinstance(self.data, - self.representation_type._unit_representation)): + if hasattr(self.representation_type, "_unit_representation") and isinstance( + self.data, self.representation_type._unit_representation + ): rep_cls = self.data.__class__ else: rep_cls = self.representation_type - if 's' in self.data.differentials: - dif_cls = self.get_representation_cls('s') - dif_data = self.data.differentials['s'] - if isinstance(dif_data, (r.UnitSphericalDifferential, - r.UnitSphericalCosLatDifferential, - r.RadialDifferential)): + if "s" in self.data.differentials: + dif_cls = self.get_representation_cls("s") + dif_data = self.data.differentials["s"] + if isinstance( + dif_data, + ( + r.UnitSphericalDifferential, + r.UnitSphericalCosLatDifferential, + r.RadialDifferential, + ), + ): dif_cls = dif_data.__class__ else: @@ -1420,42 +1519,46 @@ def _data_repr(self): data_repr = repr(data) # Generate the list of component names out of the repr string - part1, _, remainder = data_repr.partition('(') - if remainder != '': - comp_str, _, part2 = remainder.partition(')') - comp_names = comp_str.split(', ') + part1, _, remainder = data_repr.partition("(") + if remainder != "": + comp_str, _, part2 = remainder.partition(")") + comp_names = comp_str.split(", ") # Swap in frame-specific component names - invnames = {nmrepr: nmpref - for nmpref, nmrepr in self.representation_component_names.items()} + invnames = { + nmrepr: nmpref + for nmpref, nmrepr in self.representation_component_names.items() + } for i, name in enumerate(comp_names): comp_names[i] = invnames.get(name, name) # Reassemble the repr string - data_repr = part1 + '(' + ', '.join(comp_names) + ')' + part2 + data_repr = part1 + "(" + ", ".join(comp_names) + ")" + part2 else: data = self.data data_repr = repr(self.data) - if data_repr.startswith('<' + data.__class__.__name__): + if data_repr.startswith("<" + data.__class__.__name__): # remove both the leading "<" and the space after the name, as well # as the trailing ">" - data_repr = data_repr[(len(data.__class__.__name__) + 2):-1] + data_repr = data_repr[(len(data.__class__.__name__) + 2) : -1] else: - data_repr = 'Data:\n' + data_repr - - if 's' in self.data.differentials: - data_repr_spl = data_repr.split('\n') - if 'has differentials' in data_repr_spl[-1]: - diffrepr = repr(data.differentials['s']).split('\n') - if diffrepr[0].startswith('<'): - diffrepr[0] = ' ' + ' '.join(diffrepr[0].split(' ')[1:]) - for frm_nm, rep_nm in self.get_representation_component_names('s').items(): + data_repr = "Data:\n" + data_repr + + if "s" in self.data.differentials: + data_repr_spl = data_repr.split("\n") + if "has differentials" in data_repr_spl[-1]: + diffrepr = repr(data.differentials["s"]).split("\n") + if diffrepr[0].startswith("<"): + diffrepr[0] = " " + " ".join(diffrepr[0].split(" ")[1:]) + for frm_nm, rep_nm in self.get_representation_component_names( + "s" + ).items(): diffrepr[0] = diffrepr[0].replace(rep_nm, frm_nm) - if diffrepr[-1].endswith('>'): + if diffrepr[-1].endswith(">"): diffrepr[-1] = diffrepr[-1][:-1] - data_repr_spl[-1] = '\n'.join(diffrepr) + data_repr_spl[-1] = "\n".join(diffrepr) - data_repr = '\n'.join(data_repr_spl) + data_repr = "\n".join(data_repr_spl) return data_repr @@ -1475,7 +1578,7 @@ def _frame_attrs_repr(self): attrstr = str(attr) attr_strs.append(f"{attribute_name}={attrstr}") - return ', '.join(attr_strs) + return ", ".join(attr_strs) def _apply(self, method, *args, **kwargs): """Create a new instance, applying a method to the underlying data. @@ -1502,6 +1605,7 @@ def _apply(self, method, *args, **kwargs): **kwargs : dict Any keyword arguments for ``method``. """ + def apply_method(value): if isinstance(value, ShapedLikeNDArray): return value._apply(method, *args, **kwargs) @@ -1512,19 +1616,19 @@ def apply_method(value): return getattr(value, method)(*args, **kwargs) new = super().__new__(self.__class__) - if hasattr(self, '_representation'): + if hasattr(self, "_representation"): new._representation = self._representation.copy() new._attr_names_with_defaults = self._attr_names_with_defaults.copy() for attr in self.frame_attributes: - _attr = '_' + attr + _attr = "_" + attr if attr in self._attr_names_with_defaults: setattr(new, _attr, getattr(self, _attr)) else: value = getattr(self, _attr) - if getattr(value, 'shape', ()): + if getattr(value, "shape", ()): value = apply_method(value) - elif method == 'copy' or method == 'flatten': + elif method == "copy" or method == "flatten": # flatten should copy also for a single element array, but # we cannot use it directly for array scalars, since it # always returns a one-dimensional array. So, just copy. @@ -1536,13 +1640,18 @@ def apply_method(value): new._data = apply_method(self.data) else: new._data = None - shapes = [getattr(new, '_' + attr).shape - for attr in new.frame_attributes - if (attr not in new._attr_names_with_defaults - and getattr(getattr(new, '_' + attr), 'shape', ()))] + shapes = [ + getattr(new, "_" + attr).shape + for attr in new.frame_attributes + if ( + attr not in new._attr_names_with_defaults + and getattr(getattr(new, "_" + attr), "shape", ()) + ) + ] if shapes: - new._no_data_shape = (check_broadcast(*shapes) - if len(shapes) > 1 else shapes[0]) + new._no_data_shape = ( + check_broadcast(*shapes) if len(shapes) > 1 else shapes[0] + ) else: new._no_data_shape = () @@ -1550,42 +1659,48 @@ def apply_method(value): def __setitem__(self, item, value): if self.__class__ is not value.__class__: - raise TypeError(f'can only set from object of same class: ' - f'{self.__class__.__name__} vs. ' - f'{value.__class__.__name__}') + raise TypeError( + f"can only set from object of same class: {self.__class__.__name__} vs." + f" {value.__class__.__name__}" + ) if not self.is_equivalent_frame(value): - raise ValueError('can only set frame item from an equivalent frame') + raise ValueError("can only set frame item from an equivalent frame") if value._data is None: - raise ValueError('can only set frame with value that has data') + raise ValueError("can only set frame with value that has data") if self._data is None: - raise ValueError('cannot set frame which has no data') + raise ValueError("cannot set frame which has no data") if self.shape == (): - raise TypeError(f"scalar '{self.__class__.__name__}' frame object " - f"does not support item assignment") + raise TypeError( + f"scalar '{self.__class__.__name__}' frame object " + "does not support item assignment" + ) if self._data is None: - raise ValueError('can only set frame if it has data') + raise ValueError("can only set frame if it has data") if self._data.__class__ is not value._data.__class__: - raise TypeError(f'can only set from object of same class: ' - f'{self._data.__class__.__name__} vs. ' - f'{value._data.__class__.__name__}') + raise TypeError( + "can only set from object of same class: " + f"{self._data.__class__.__name__} vs. {value._data.__class__.__name__}" + ) if self._data._differentials: # Can this ever occur? (Same class but different differential keys). # This exception is not tested since it is not clear how to generate it. if self._data._differentials.keys() != value._data._differentials.keys(): - raise ValueError('setitem value must have same differentials') + raise ValueError("setitem value must have same differentials") for key, self_diff in self._data._differentials.items(): if self_diff.__class__ is not value._data._differentials[key].__class__: - raise TypeError(f'can only set from object of same class: ' - f'{self_diff.__class__.__name__} vs. ' - f'{value._data._differentials[key].__class__.__name__}') + raise TypeError( + "can only set from object of same class: " + f"{self_diff.__class__.__name__} vs. " + f"{value._data._differentials[key].__class__.__name__}" + ) # Set representation data self._data[item] = value._data @@ -1605,7 +1720,7 @@ def __dir__(self): return sorted( set(super().__dir__()) | set(self.representation_component_names) - | set(self.get_representation_component_names('s')) + | set(self.get_representation_component_names("s")) ) def __getattr__(self, attr): @@ -1622,7 +1737,7 @@ def __getattr__(self, attr): # self.representation_component_names. # # Prevent infinite recursion here. - if attr.startswith('_'): + if attr.startswith("_"): return self.__getattribute__(attr) # Raise AttributeError. repr_names = self.representation_component_names @@ -1631,12 +1746,11 @@ def __getattr__(self, attr): self.data # this raises the "no data" error by design - doing it # this way means we don't have to replicate the error message here - rep = self.represent_as(self.representation_type, - in_frame_units=True) + rep = self.represent_as(self.representation_type, in_frame_units=True) val = getattr(rep, repr_names[attr]) return val - diff_names = self.get_representation_component_names('s') + diff_names = self.get_representation_component_names("s") if attr in diff_names: if self._data is None: self.data # see above. @@ -1644,24 +1758,24 @@ def __getattr__(self, attr): # unitspherical information. The differential_type gets set to the # default_differential, which expects full information, so the # units don't work out - rep = self.represent_as(in_frame_units=True, - **self.get_representation_cls(None)) - val = getattr(rep.differentials['s'], diff_names[attr]) + rep = self.represent_as( + in_frame_units=True, **self.get_representation_cls(None) + ) + val = getattr(rep.differentials["s"], diff_names[attr]) return val return self.__getattribute__(attr) # Raise AttributeError. def __setattr__(self, attr, value): # Don't slow down access of private attributes! - if not attr.startswith('_'): - if hasattr(self, 'representation_info'): + if not attr.startswith("_"): + if hasattr(self, "representation_info"): repr_attr_names = set() for representation_attr in self.representation_info.values(): - repr_attr_names.update(representation_attr['names']) + repr_attr_names.update(representation_attr["names"]) if attr in repr_attr_names: - raise AttributeError( - f'Cannot set any frame attribute {attr}') + raise AttributeError(f"Cannot set any frame attribute {attr}") super().__setattr__(attr, value) @@ -1681,13 +1795,15 @@ def __eq__(self, value): return is_equiv if not is_equiv: - raise TypeError(f'cannot compare: objects must have equivalent frames: ' - f'{self.replicate_without_data()} vs. ' - f'{value.replicate_without_data()}') + raise TypeError( + "cannot compare: objects must have equivalent frames: " + f"{self.replicate_without_data()} vs. {value.replicate_without_data()}" + ) if (value._data is None) != (self._data is None): - raise ValueError('cannot compare: one frame has data and the other ' - 'does not') + raise ValueError( + "cannot compare: one frame has data and the other does not" + ) return self._data == value._data @@ -1732,8 +1848,9 @@ def separation(self, other): other_unit_sph = other_transformed.represent_as(r.UnitSphericalRepresentation) # Get the separation as a Quantity, convert to Angle in degrees - sep = angular_separation(self_unit_sph.lon, self_unit_sph.lat, - other_unit_sph.lon, other_unit_sph.lat) + sep = angular_separation( + self_unit_sph.lon, self_unit_sph.lat, other_unit_sph.lon, other_unit_sph.lat + ) return Angle(sep, unit=u.degree) def separation_3d(self, other): @@ -1760,20 +1877,27 @@ def separation_3d(self, other): from .distances import Distance if issubclass(self.data.__class__, r.UnitSphericalRepresentation): - raise ValueError('This object does not have a distance; cannot ' - 'compute 3d separation.') + raise ValueError( + "This object does not have a distance; cannot compute 3d separation." + ) # do this first just in case the conversion somehow creates a distance other_in_self_system = other.transform_to(self) if issubclass(other_in_self_system.__class__, r.UnitSphericalRepresentation): - raise ValueError('The other object does not have a distance; ' - 'cannot compute 3d separation.') + raise ValueError( + "The other object does not have a distance; " + "cannot compute 3d separation." + ) # drop the differentials to ensure they don't do anything odd in the # subtraction - self_car = self.data.without_differentials().represent_as(r.CartesianRepresentation) - other_car = other_in_self_system.data.without_differentials().represent_as(r.CartesianRepresentation) + self_car = self.data.without_differentials().represent_as( + r.CartesianRepresentation + ) + other_car = other_in_self_system.data.without_differentials().represent_as( + r.CartesianRepresentation + ) dist = (self_car - other_car).norm() if dist.unit == u.one: return dist @@ -1789,7 +1913,7 @@ def cartesian(self): # TODO: if representations are updated to use a full transform graph, # the representation aliases should not be hard-coded like this - return self.represent_as('cartesian', in_frame_units=True) + return self.represent_as("cartesian", in_frame_units=True) @property def cylindrical(self): @@ -1800,7 +1924,7 @@ def cylindrical(self): # TODO: if representations are updated to use a full transform graph, # the representation aliases should not be hard-coded like this - return self.represent_as('cylindrical', in_frame_units=True) + return self.represent_as("cylindrical", in_frame_units=True) @property def spherical(self): @@ -1811,7 +1935,7 @@ def spherical(self): # TODO: if representations are updated to use a full transform graph, # the representation aliases should not be hard-coded like this - return self.represent_as('spherical', in_frame_units=True) + return self.represent_as("spherical", in_frame_units=True) @property def sphericalcoslat(self): @@ -1822,8 +1946,7 @@ def sphericalcoslat(self): # TODO: if representations are updated to use a full transform graph, # the representation aliases should not be hard-coded like this - return self.represent_as('spherical', 'sphericalcoslat', - in_frame_units=True) + return self.represent_as("spherical", "sphericalcoslat", in_frame_units=True) @property def velocity(self): @@ -1832,11 +1955,12 @@ def velocity(self): `CartesianDifferential` object. This is equivalent to calling ``self.cartesian.differentials['s']``. """ - if 's' not in self.data.differentials: - raise ValueError('Frame has no associated velocity (Differential) ' - 'data information.') + if "s" not in self.data.differentials: + raise ValueError( + "Frame has no associated velocity (Differential) data information." + ) - return self.cartesian.differentials['s'] + return self.cartesian.differentials["s"] @property def proper_motion(self): @@ -1848,16 +1972,17 @@ def proper_motion(self): motion and ``.proper_motion[1]`` is latitudinal. The longitudinal proper motion already includes the cos(latitude) term. """ - if 's' not in self.data.differentials: - raise ValueError('Frame has no associated velocity (Differential) ' - 'data information.') - - sph = self.represent_as('spherical', 'sphericalcoslat', - in_frame_units=True) - pm_lon = sph.differentials['s'].d_lon_coslat - pm_lat = sph.differentials['s'].d_lat - return np.stack((pm_lon.value, - pm_lat.to(pm_lon.unit).value), axis=0) * pm_lon.unit + if "s" not in self.data.differentials: + raise ValueError( + "Frame has no associated velocity (Differential) data information." + ) + + sph = self.represent_as("spherical", "sphericalcoslat", in_frame_units=True) + pm_lon = sph.differentials["s"].d_lon_coslat + pm_lat = sph.differentials["s"].d_lat + return ( + np.stack((pm_lon.value, pm_lat.to(pm_lon.unit).value), axis=0) * pm_lon.unit + ) @property def radial_velocity(self): @@ -1865,12 +1990,13 @@ def radial_velocity(self): Shorthand for the radial or line-of-sight velocity as a `~astropy.units.Quantity` object. """ - if 's' not in self.data.differentials: - raise ValueError('Frame has no associated velocity (Differential) ' - 'data information.') + if "s" not in self.data.differentials: + raise ValueError( + "Frame has no associated velocity (Differential) data information." + ) - sph = self.represent_as('spherical', in_frame_units=True) - return sph.differentials['s'].d_distance + sph = self.represent_as("spherical", in_frame_units=True) + return sph.differentials["s"].d_distance class GenericFrame(BaseCoordinateFrame): @@ -1892,15 +2018,15 @@ def __init__(self, frame_attrs): self.frame_attributes = {} for name, default in frame_attrs.items(): self.frame_attributes[name] = Attribute(default) - setattr(self, '_' + name, default) + setattr(self, "_" + name, default) super().__init__(None) def __getattr__(self, name): - if '_' + name in self.__dict__: - return getattr(self, '_' + name) + if "_" + name in self.__dict__: + return getattr(self, "_" + name) else: - raise AttributeError(f'no {name}') + raise AttributeError(f"no {name}") def __setattr__(self, name, value): if name in self.frame_attributes: diff --git a/astropy/coordinates/builtin_frames/__init__.py b/astropy/coordinates/builtin_frames/__init__.py index 7718151a434..75ae6b26392 100644 --- a/astropy/coordinates/builtin_frames/__init__.py +++ b/astropy/coordinates/builtin_frames/__init__.py @@ -76,16 +76,41 @@ # we define an __all__ because otherwise the transformation modules # get included -__all__ = ['ICRS', 'FK5', 'FK4', 'FK4NoETerms', 'Galactic', 'Galactocentric', - 'galactocentric_frame_defaults', - 'Supergalactic', 'AltAz', 'HADec', 'GCRS', 'CIRS', 'ITRS', 'HCRS', - 'TEME', 'TETE', 'PrecessedGeocentric', 'GeocentricMeanEcliptic', - 'BarycentricMeanEcliptic', 'HeliocentricMeanEcliptic', - 'GeocentricTrueEcliptic', 'BarycentricTrueEcliptic', - 'HeliocentricTrueEcliptic', - 'SkyOffsetFrame', 'GalacticLSR', 'LSR', 'LSRK', 'LSRD', - 'BaseEclipticFrame', 'BaseRADecFrame', 'make_transform_graph_docs', - 'HeliocentricEclipticIAU76', 'CustomBarycentricEcliptic'] +__all__ = [ + "ICRS", + "FK5", + "FK4", + "FK4NoETerms", + "Galactic", + "Galactocentric", + "galactocentric_frame_defaults", + "Supergalactic", + "AltAz", + "HADec", + "GCRS", + "CIRS", + "ITRS", + "HCRS", + "TEME", + "TETE", + "PrecessedGeocentric", + "GeocentricMeanEcliptic", + "BarycentricMeanEcliptic", + "HeliocentricMeanEcliptic", + "GeocentricTrueEcliptic", + "BarycentricTrueEcliptic", + "HeliocentricTrueEcliptic", + "SkyOffsetFrame", + "GalacticLSR", + "LSR", + "LSRK", + "LSRD", + "BaseEclipticFrame", + "BaseRADecFrame", + "make_transform_graph_docs", + "HeliocentricEclipticIAU76", + "CustomBarycentricEcliptic", +] def make_transform_graph_docs(transform_graph): @@ -105,13 +130,12 @@ def make_transform_graph_docs(transform_graph): transform graph. """ from textwrap import dedent - coosys = [transform_graph.lookup_name(item) for - item in transform_graph.get_names()] + + coosys = [transform_graph.lookup_name(item) for item in transform_graph.get_names()] # currently, all of the priorities are set to 1, so we don't need to show # then in the transform graph. - graphstr = transform_graph.to_dot_graph(addnodes=coosys, - priorities=False) + graphstr = transform_graph.to_dot_graph(addnodes=coosys, priorities=False) docstr = """ The diagram below shows all of the built in coordinate systems, @@ -132,10 +156,11 @@ def make_transform_graph_docs(transform_graph): """ - docstr = dedent(docstr) + ' ' + graphstr.replace('\n', '\n ') + docstr = dedent(docstr) + " " + graphstr.replace("\n", "\n ") # colors are in dictionary at the bottom of transformations.py from astropy.coordinates.transformations import trans_to_color + html_list_items = [] for cls, color in trans_to_color.items(): block = f""" @@ -148,7 +173,7 @@ def make_transform_graph_docs(transform_graph): """ # noqa: E501 html_list_items.append(block) - nl = '\n' + nl = "\n" graph_legend = f""" .. raw:: html diff --git a/astropy/coordinates/builtin_frames/altaz.py b/astropy/coordinates/builtin_frames/altaz.py index 7ff6f1e045b..5ec712948e7 100644 --- a/astropy/coordinates/builtin_frames/altaz.py +++ b/astropy/coordinates/builtin_frames/altaz.py @@ -16,10 +16,10 @@ ) from astropy.utils.decorators import format_doc -__all__ = ['AltAz'] +__all__ = ["AltAz"] -_90DEG = 90*u.deg +_90DEG = 90 * u.deg doc_components = """ az : `~astropy.coordinates.Angle`, optional, keyword-only @@ -95,8 +95,8 @@ class AltAz(BaseCoordinateFrame): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'az'), - RepresentationMapping('lat', 'alt') + RepresentationMapping("lon", "az"), + RepresentationMapping("lat", "alt"), ] } @@ -108,7 +108,7 @@ class AltAz(BaseCoordinateFrame): pressure = QuantityAttribute(default=0, unit=u.hPa) temperature = QuantityAttribute(default=0, unit=u.deg_C) relative_humidity = QuantityAttribute(default=0, unit=u.dimensionless_unscaled) - obswl = QuantityAttribute(default=1*u.micron, unit=u.micron) + obswl = QuantityAttribute(default=1 * u.micron, unit=u.micron) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -119,7 +119,7 @@ def secz(self): Secant of the zenith angle for this coordinate, a common estimate of the airmass. """ - return 1/np.sin(self.alt) + return 1 / np.sin(self.alt) @property def zen(self): diff --git a/astropy/coordinates/builtin_frames/baseradec.py b/astropy/coordinates/builtin_frames/baseradec.py index 7f57604fe46..ad3811575c9 100644 --- a/astropy/coordinates/builtin_frames/baseradec.py +++ b/astropy/coordinates/builtin_frames/baseradec.py @@ -8,7 +8,7 @@ ) from astropy.utils.decorators import format_doc -__all__ = ['BaseRADecFrame'] +__all__ = ["BaseRADecFrame"] doc_components = """ @@ -40,10 +40,11 @@ class BaseRADecFrame(BaseCoordinateFrame): represent longitude and latitude as Right Ascension and Declination following typical "equatorial" conventions. """ + frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'ra'), - RepresentationMapping('lat', 'dec') + RepresentationMapping("lon", "ra"), + RepresentationMapping("lat", "dec"), ] } diff --git a/astropy/coordinates/builtin_frames/cirs.py b/astropy/coordinates/builtin_frames/cirs.py index c22dcd81d59..21fc8d36f06 100644 --- a/astropy/coordinates/builtin_frames/cirs.py +++ b/astropy/coordinates/builtin_frames/cirs.py @@ -7,7 +7,7 @@ from .baseradec import BaseRADecFrame, doc_components from .utils import DEFAULT_OBSTIME, EARTH_CENTER -__all__ = ['CIRS'] +__all__ = ["CIRS"] doc_footer = """ @@ -35,5 +35,6 @@ class CIRS(BaseRADecFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) location = EarthLocationAttribute(default=EARTH_CENTER) + # The "self-transform" is defined in icrs_cirs_transformations.py, because in # the current implementation it goes through ICRS (like GCRS) diff --git a/astropy/coordinates/builtin_frames/cirs_observed_transforms.py b/astropy/coordinates/builtin_frames/cirs_observed_transforms.py index d2a33593e18..1c93f85883b 100644 --- a/astropy/coordinates/builtin_frames/cirs_observed_transforms.py +++ b/astropy/coordinates/builtin_frames/cirs_observed_transforms.py @@ -24,14 +24,18 @@ @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, CIRS, AltAz) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, CIRS, HADec) def cirs_to_observed(cirs_coo, observed_frame): - if (np.any(observed_frame.location != cirs_coo.location) or - np.any(cirs_coo.obstime != observed_frame.obstime)): - cirs_coo = cirs_coo.transform_to(CIRS(obstime=observed_frame.obstime, - location=observed_frame.location)) + if np.any(observed_frame.location != cirs_coo.location) or np.any( + cirs_coo.obstime != observed_frame.obstime + ): + cirs_coo = cirs_coo.transform_to( + CIRS(obstime=observed_frame.obstime, location=observed_frame.location) + ) # if the data are UnitSphericalRepresentation, we can skip the distance calculations - is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation) or - cirs_coo.cartesian.x.unit == u.one) + is_unitspherical = ( + isinstance(cirs_coo.data, UnitSphericalRepresentation) + or cirs_coo.cartesian.x.unit == u.one + ) # We used to do "astrometric" corrections here, but these are no longer necesssary # CIRS has proper topocentric behaviour @@ -48,15 +52,19 @@ def cirs_to_observed(cirs_coo, observed_frame): _, _, lon, lat, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) if is_unitspherical: - rep = UnitSphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False), - lon=u.Quantity(lon, u.radian, copy=False), - copy=False) + rep = UnitSphericalRepresentation( + lat=u.Quantity(lat, u.radian, copy=False), + lon=u.Quantity(lon, u.radian, copy=False), + copy=False, + ) else: # since we've transformed to CIRS at the observatory location, just use CIRS distance - rep = SphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False), - lon=u.Quantity(lon, u.radian, copy=False), - distance=cirs_coo.distance, - copy=False) + rep = SphericalRepresentation( + lat=u.Quantity(lat, u.radian, copy=False), + lon=u.Quantity(lon, u.radian, copy=False), + distance=cirs_coo.distance, + copy=False, + ) return observed_frame.realize_frame(rep) @@ -69,23 +77,30 @@ def observed_to_cirs(observed_coo, cirs_frame): if isinstance(observed_coo, AltAz): # the 'A' indicates zen/az inputs - coord_type = 'A' + coord_type = "A" lat = PIOVER2 - lat else: - coord_type = 'H' + coord_type = "H" # first set up the astrometry context for ICRS<->CIRS at the observed_coo time astrom = erfa_astrom.get().apio(observed_coo) cirs_ra, cirs_dec = erfa.atoiq(coord_type, lon, lat, astrom) << u.radian - if isinstance(observed_coo.data, UnitSphericalRepresentation) or observed_coo.cartesian.x.unit == u.one: + if ( + isinstance(observed_coo.data, UnitSphericalRepresentation) + or observed_coo.cartesian.x.unit == u.one + ): distance = None else: distance = observed_coo.distance - cirs_at_aa_time = CIRS(ra=cirs_ra, dec=cirs_dec, distance=distance, - obstime=observed_coo.obstime, - location=observed_coo.location) + cirs_at_aa_time = CIRS( + ra=cirs_ra, + dec=cirs_dec, + distance=distance, + obstime=observed_coo.obstime, + location=observed_coo.location, + ) # this final transform may be a no-op if the obstimes and locations are the same return cirs_at_aa_time.transform_to(cirs_frame) diff --git a/astropy/coordinates/builtin_frames/ecliptic.py b/astropy/coordinates/builtin_frames/ecliptic.py index 9b78513c73f..c766c267169 100644 --- a/astropy/coordinates/builtin_frames/ecliptic.py +++ b/astropy/coordinates/builtin_frames/ecliptic.py @@ -8,11 +8,17 @@ from .utils import DEFAULT_OBSTIME, EQUINOX_J2000 -__all__ = ['GeocentricMeanEcliptic', 'BarycentricMeanEcliptic', - 'HeliocentricMeanEcliptic', 'BaseEclipticFrame', - 'GeocentricTrueEcliptic', 'BarycentricTrueEcliptic', - 'HeliocentricTrueEcliptic', - 'HeliocentricEclipticIAU76', 'CustomBarycentricEcliptic'] +__all__ = [ + "GeocentricMeanEcliptic", + "BarycentricMeanEcliptic", + "HeliocentricMeanEcliptic", + "BaseEclipticFrame", + "GeocentricTrueEcliptic", + "BarycentricTrueEcliptic", + "HeliocentricTrueEcliptic", + "HeliocentricEclipticIAU76", + "CustomBarycentricEcliptic", +] doc_components_ecl = """ @@ -37,9 +43,9 @@ """ -@format_doc(base_doc, - components=doc_components_ecl.format('specified location'), - footer="") +@format_doc( + base_doc, components=doc_components_ecl.format("specified location"), footer="" +) class BaseEclipticFrame(BaseCoordinateFrame): """ A base class for frames that have names and conventions like that of @@ -69,8 +75,9 @@ class BaseEclipticFrame(BaseCoordinateFrame): """ -@format_doc(base_doc, components=doc_components_ecl.format('geocenter'), - footer=doc_footer_geo) +@format_doc( + base_doc, components=doc_components_ecl.format("geocenter"), footer=doc_footer_geo +) class GeocentricMeanEcliptic(BaseEclipticFrame): """ Geocentric mean ecliptic coordinates. These origin of the coordinates are the @@ -89,8 +96,9 @@ class GeocentricMeanEcliptic(BaseEclipticFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) -@format_doc(base_doc, components=doc_components_ecl.format('geocenter'), - footer=doc_footer_geo) +@format_doc( + base_doc, components=doc_components_ecl.format("geocenter"), footer=doc_footer_geo +) class GeocentricTrueEcliptic(BaseEclipticFrame): """ Geocentric true ecliptic coordinates. These origin of the coordinates are the @@ -119,8 +127,9 @@ class GeocentricTrueEcliptic(BaseEclipticFrame): """ -@format_doc(base_doc, components=doc_components_ecl.format("barycenter"), - footer=doc_footer_bary) +@format_doc( + base_doc, components=doc_components_ecl.format("barycenter"), footer=doc_footer_bary +) class BarycentricMeanEcliptic(BaseEclipticFrame): """ Barycentric mean ecliptic coordinates. These origin of the coordinates are the @@ -135,8 +144,9 @@ class BarycentricMeanEcliptic(BaseEclipticFrame): equinox = TimeAttribute(default=EQUINOX_J2000) -@format_doc(base_doc, components=doc_components_ecl.format("barycenter"), - footer=doc_footer_bary) +@format_doc( + base_doc, components=doc_components_ecl.format("barycenter"), footer=doc_footer_bary +) class BarycentricTrueEcliptic(BaseEclipticFrame): """ Barycentric true ecliptic coordinates. These origin of the coordinates are the @@ -164,8 +174,11 @@ class BarycentricTrueEcliptic(BaseEclipticFrame): """ -@format_doc(base_doc, components=doc_components_ecl.format("sun's center"), - footer=doc_footer_helio) +@format_doc( + base_doc, + components=doc_components_ecl.format("sun's center"), + footer=doc_footer_helio, +) class HeliocentricMeanEcliptic(BaseEclipticFrame): """ Heliocentric mean ecliptic coordinates. These origin of the coordinates are the @@ -185,8 +198,11 @@ class HeliocentricMeanEcliptic(BaseEclipticFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) -@format_doc(base_doc, components=doc_components_ecl.format("sun's center"), - footer=doc_footer_helio) +@format_doc( + base_doc, + components=doc_components_ecl.format("sun's center"), + footer=doc_footer_helio, +) class HeliocentricTrueEcliptic(BaseEclipticFrame): """ Heliocentric true ecliptic coordinates. These origin of the coordinates are the @@ -206,8 +222,7 @@ class HeliocentricTrueEcliptic(BaseEclipticFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) -@format_doc(base_doc, components=doc_components_ecl.format("sun's center"), - footer="") +@format_doc(base_doc, components=doc_components_ecl.format("sun's center"), footer="") class HeliocentricEclipticIAU76(BaseEclipticFrame): """ Heliocentric mean (IAU 1976) ecliptic coordinates. These origin of the coordinates are the @@ -227,8 +242,7 @@ class HeliocentricEclipticIAU76(BaseEclipticFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) -@format_doc(base_doc, components=doc_components_ecl.format("barycenter"), - footer="") +@format_doc(base_doc, components=doc_components_ecl.format("barycenter"), footer="") class CustomBarycentricEcliptic(BaseEclipticFrame): """ Barycentric ecliptic coordinates with custom obliquity. diff --git a/astropy/coordinates/builtin_frames/ecliptic_transforms.py b/astropy/coordinates/builtin_frames/ecliptic_transforms.py index e9c94899900..4d624e020e5 100644 --- a/astropy/coordinates/builtin_frames/ecliptic_transforms.py +++ b/astropy/coordinates/builtin_frames/ecliptic_transforms.py @@ -33,7 +33,7 @@ def _mean_ecliptic_rotation_matrix(equinox): # This code just calls ecm06, which uses the precession matrix according to the # IAU 2006 model, but leaves out nutation. This brings the results closer to what # other libraries give (see https://github.com/astropy/astropy/pull/6508). - return erfa.ecm06(*get_jd12(equinox, 'tt')) + return erfa.ecm06(*get_jd12(equinox, "tt")) def _true_ecliptic_rotation_matrix(equinox): @@ -42,7 +42,7 @@ def _true_ecliptic_rotation_matrix(equinox): # the IAU 2006 model, and including the nutation. # This family of systems is less popular # (see https://github.com/astropy/astropy/pull/6508). - jd1, jd2 = get_jd12(equinox, 'tt') + jd1, jd2 = get_jd12(equinox, "tt") # Here, we call the three routines from erfa.pnm06a separately, # so that we can keep the nutation for calculating the true obliquity # (which is a fairly expensive operation); see gh-11000. @@ -52,13 +52,15 @@ def _true_ecliptic_rotation_matrix(equinox): # pnm06a: Nutation components (in longitude and obliquity). dpsi, deps = erfa.nut06a(jd1, jd2) # pnm06a: Equinox based nutation x precession x bias matrix. - rnpb = erfa.fw2m(gamb, phib, psib+dpsi, epsa+deps) + rnpb = erfa.fw2m(gamb, phib, psib + dpsi, epsa + deps) # calculate the true obliquity of the ecliptic - obl = erfa.obl06(jd1, jd2)+deps - return rotation_matrix(obl << u.radian, 'x') @ rnpb + obl = erfa.obl06(jd1, jd2) + deps + return rotation_matrix(obl << u.radian, "x") @ rnpb -def _obliquity_only_rotation_matrix(obl=erfa.obl80(EQUINOX_J2000.jd1, EQUINOX_J2000.jd2) * u.radian): +def _obliquity_only_rotation_matrix( + obl=erfa.obl80(EQUINOX_J2000.jd1, EQUINOX_J2000.jd2) * u.radian +): # This code only accounts for the obliquity, # which can be passed explicitly. # The default value is the IAU 1980 value for J2000, @@ -71,9 +73,12 @@ def _obliquity_only_rotation_matrix(obl=erfa.obl80(EQUINOX_J2000.jd1, EQUINOX_J2 # MeanEcliptic frames -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - GCRS, GeocentricMeanEcliptic, - finite_difference_frameattr_name='equinox') +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + GCRS, + GeocentricMeanEcliptic, + finite_difference_frameattr_name="equinox", +) def gcrs_to_geoecliptic(gcrs_coo, to_frame): # first get us to a 0 pos/vel GCRS at the target equinox gcrs_coo2 = gcrs_coo.transform_to(GCRS(obstime=to_frame.obstime)) @@ -83,7 +88,9 @@ def gcrs_to_geoecliptic(gcrs_coo, to_frame): return to_frame.realize_frame(newrepr) -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GeocentricMeanEcliptic, GCRS) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, GeocentricMeanEcliptic, GCRS +) def geoecliptic_to_gcrs(from_coo, gcrs_frame): rmat = _mean_ecliptic_rotation_matrix(from_coo.equinox) newrepr = from_coo.cartesian.transform(matrix_transpose(rmat)) @@ -103,21 +110,24 @@ def baryecliptic_to_icrs(from_coo, to_frame): return matrix_transpose(icrs_to_baryecliptic(to_frame, from_coo)) -_NEED_ORIGIN_HINT = ("The input {0} coordinates do not have length units. This " - "probably means you created coordinates with lat/lon but " - "no distance. Heliocentric<->ICRS transforms cannot " - "function in this case because there is an origin shift.") +_NEED_ORIGIN_HINT = ( + "The input {0} coordinates do not have length units. This probably means you" + " created coordinates with lat/lon but no distance. Heliocentric<->ICRS transforms" + " cannot function in this case because there is an origin shift." +) -@frame_transform_graph.transform(AffineTransform, - ICRS, HeliocentricMeanEcliptic) +@frame_transform_graph.transform(AffineTransform, ICRS, HeliocentricMeanEcliptic) def icrs_to_helioecliptic(from_coo, to_frame): if not u.m.is_equivalent(from_coo.cartesian.x.unit): raise UnitsError(_NEED_ORIGIN_HINT.format(from_coo.__class__.__name__)) # get the offset of the barycenter from the Sun - ssb_from_sun = get_offset_sun_from_barycenter(to_frame.obstime, reverse=True, - include_velocity=bool(from_coo.data.differentials)) + ssb_from_sun = get_offset_sun_from_barycenter( + to_frame.obstime, + reverse=True, + include_velocity=bool(from_coo.data.differentials), + ) # now compute the matrix to precess to the right orientation rmat = _mean_ecliptic_rotation_matrix(to_frame.equinox) @@ -125,8 +135,7 @@ def icrs_to_helioecliptic(from_coo, to_frame): return rmat, ssb_from_sun.transform(rmat) -@frame_transform_graph.transform(AffineTransform, - HeliocentricMeanEcliptic, ICRS) +@frame_transform_graph.transform(AffineTransform, HeliocentricMeanEcliptic, ICRS) def helioecliptic_to_icrs(from_coo, to_frame): if not u.m.is_equivalent(from_coo.cartesian.x.unit): raise UnitsError(_NEED_ORIGIN_HINT.format(from_coo.__class__.__name__)) @@ -135,8 +144,9 @@ def helioecliptic_to_icrs(from_coo, to_frame): rmat = _mean_ecliptic_rotation_matrix(from_coo.equinox) # now offset back to barycentric, which is the correct center for ICRS - sun_from_ssb = get_offset_sun_from_barycenter(from_coo.obstime, - include_velocity=bool(from_coo.data.differentials)) + sun_from_ssb = get_offset_sun_from_barycenter( + from_coo.obstime, include_velocity=bool(from_coo.data.differentials) + ) return matrix_transpose(rmat), sun_from_ssb @@ -144,9 +154,12 @@ def helioecliptic_to_icrs(from_coo, to_frame): # TrueEcliptic frames -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - GCRS, GeocentricTrueEcliptic, - finite_difference_frameattr_name='equinox') +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + GCRS, + GeocentricTrueEcliptic, + finite_difference_frameattr_name="equinox", +) def gcrs_to_true_geoecliptic(gcrs_coo, to_frame): # first get us to a 0 pos/vel GCRS at the target equinox gcrs_coo2 = gcrs_coo.transform_to(GCRS(obstime=to_frame.obstime)) @@ -156,7 +169,9 @@ def gcrs_to_true_geoecliptic(gcrs_coo, to_frame): return to_frame.realize_frame(newrepr) -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GeocentricTrueEcliptic, GCRS) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, GeocentricTrueEcliptic, GCRS +) def true_geoecliptic_to_gcrs(from_coo, gcrs_frame): rmat = _true_ecliptic_rotation_matrix(from_coo.equinox) newrepr = from_coo.cartesian.transform(matrix_transpose(rmat)) @@ -176,15 +191,17 @@ def true_baryecliptic_to_icrs(from_coo, to_frame): return matrix_transpose(icrs_to_true_baryecliptic(to_frame, from_coo)) -@frame_transform_graph.transform(AffineTransform, - ICRS, HeliocentricTrueEcliptic) +@frame_transform_graph.transform(AffineTransform, ICRS, HeliocentricTrueEcliptic) def icrs_to_true_helioecliptic(from_coo, to_frame): if not u.m.is_equivalent(from_coo.cartesian.x.unit): raise UnitsError(_NEED_ORIGIN_HINT.format(from_coo.__class__.__name__)) # get the offset of the barycenter from the Sun - ssb_from_sun = get_offset_sun_from_barycenter(to_frame.obstime, reverse=True, - include_velocity=bool(from_coo.data.differentials)) + ssb_from_sun = get_offset_sun_from_barycenter( + to_frame.obstime, + reverse=True, + include_velocity=bool(from_coo.data.differentials), + ) # now compute the matrix to precess to the right orientation rmat = _true_ecliptic_rotation_matrix(to_frame.equinox) @@ -192,8 +209,7 @@ def icrs_to_true_helioecliptic(from_coo, to_frame): return rmat, ssb_from_sun.transform(rmat) -@frame_transform_graph.transform(AffineTransform, - HeliocentricTrueEcliptic, ICRS) +@frame_transform_graph.transform(AffineTransform, HeliocentricTrueEcliptic, ICRS) def true_helioecliptic_to_icrs(from_coo, to_frame): if not u.m.is_equivalent(from_coo.cartesian.x.unit): raise UnitsError(_NEED_ORIGIN_HINT.format(from_coo.__class__.__name__)) @@ -202,8 +218,9 @@ def true_helioecliptic_to_icrs(from_coo, to_frame): rmat = _true_ecliptic_rotation_matrix(from_coo.equinox) # now offset back to barycentric, which is the correct center for ICRS - sun_from_ssb = get_offset_sun_from_barycenter(from_coo.obstime, - include_velocity=bool(from_coo.data.differentials)) + sun_from_ssb = get_offset_sun_from_barycenter( + from_coo.obstime, include_velocity=bool(from_coo.data.differentials) + ) return matrix_transpose(rmat), sun_from_ssb @@ -211,25 +228,27 @@ def true_helioecliptic_to_icrs(from_coo, to_frame): # Other ecliptic frames -@frame_transform_graph.transform(AffineTransform, - HeliocentricEclipticIAU76, ICRS) +@frame_transform_graph.transform(AffineTransform, HeliocentricEclipticIAU76, ICRS) def ecliptic_to_iau76_icrs(from_coo, to_frame): # first un-precess from ecliptic to ICRS orientation rmat = _obliquity_only_rotation_matrix() # now offset back to barycentric, which is the correct center for ICRS - sun_from_ssb = get_offset_sun_from_barycenter(from_coo.obstime, - include_velocity=bool(from_coo.data.differentials)) + sun_from_ssb = get_offset_sun_from_barycenter( + from_coo.obstime, include_velocity=bool(from_coo.data.differentials) + ) return matrix_transpose(rmat), sun_from_ssb -@frame_transform_graph.transform(AffineTransform, - ICRS, HeliocentricEclipticIAU76) +@frame_transform_graph.transform(AffineTransform, ICRS, HeliocentricEclipticIAU76) def icrs_to_iau76_ecliptic(from_coo, to_frame): # get the offset of the barycenter from the Sun - ssb_from_sun = get_offset_sun_from_barycenter(to_frame.obstime, reverse=True, - include_velocity=bool(from_coo.data.differentials)) + ssb_from_sun = get_offset_sun_from_barycenter( + to_frame.obstime, + reverse=True, + include_velocity=bool(from_coo.data.differentials), + ) # now compute the matrix to precess to the right orientation rmat = _obliquity_only_rotation_matrix() @@ -237,24 +256,42 @@ def icrs_to_iau76_ecliptic(from_coo, to_frame): return rmat, ssb_from_sun.transform(rmat) -@frame_transform_graph.transform(DynamicMatrixTransform, - ICRS, CustomBarycentricEcliptic) +@frame_transform_graph.transform( + DynamicMatrixTransform, ICRS, CustomBarycentricEcliptic +) def icrs_to_custombaryecliptic(from_coo, to_frame): return _obliquity_only_rotation_matrix(to_frame.obliquity) -@frame_transform_graph.transform(DynamicMatrixTransform, - CustomBarycentricEcliptic, ICRS) +@frame_transform_graph.transform( + DynamicMatrixTransform, CustomBarycentricEcliptic, ICRS +) def custombaryecliptic_to_icrs(from_coo, to_frame): return icrs_to_custombaryecliptic(to_frame, from_coo).T # Create loopback transformations -frame_transform_graph._add_merged_transform(GeocentricMeanEcliptic, ICRS, GeocentricMeanEcliptic) -frame_transform_graph._add_merged_transform(GeocentricTrueEcliptic, ICRS, GeocentricTrueEcliptic) -frame_transform_graph._add_merged_transform(HeliocentricMeanEcliptic, ICRS, HeliocentricMeanEcliptic) -frame_transform_graph._add_merged_transform(HeliocentricTrueEcliptic, ICRS, HeliocentricTrueEcliptic) -frame_transform_graph._add_merged_transform(HeliocentricEclipticIAU76, ICRS, HeliocentricEclipticIAU76) -frame_transform_graph._add_merged_transform(BarycentricMeanEcliptic, ICRS, BarycentricMeanEcliptic) -frame_transform_graph._add_merged_transform(BarycentricTrueEcliptic, ICRS, BarycentricTrueEcliptic) -frame_transform_graph._add_merged_transform(CustomBarycentricEcliptic, ICRS, CustomBarycentricEcliptic) +frame_transform_graph._add_merged_transform( + GeocentricMeanEcliptic, ICRS, GeocentricMeanEcliptic +) +frame_transform_graph._add_merged_transform( + GeocentricTrueEcliptic, ICRS, GeocentricTrueEcliptic +) +frame_transform_graph._add_merged_transform( + HeliocentricMeanEcliptic, ICRS, HeliocentricMeanEcliptic +) +frame_transform_graph._add_merged_transform( + HeliocentricTrueEcliptic, ICRS, HeliocentricTrueEcliptic +) +frame_transform_graph._add_merged_transform( + HeliocentricEclipticIAU76, ICRS, HeliocentricEclipticIAU76 +) +frame_transform_graph._add_merged_transform( + BarycentricMeanEcliptic, ICRS, BarycentricMeanEcliptic +) +frame_transform_graph._add_merged_transform( + BarycentricTrueEcliptic, ICRS, BarycentricTrueEcliptic +) +frame_transform_graph._add_merged_transform( + CustomBarycentricEcliptic, ICRS, CustomBarycentricEcliptic +) diff --git a/astropy/coordinates/builtin_frames/equatorial.py b/astropy/coordinates/builtin_frames/equatorial.py index 5906cad0028..11d47023124 100644 --- a/astropy/coordinates/builtin_frames/equatorial.py +++ b/astropy/coordinates/builtin_frames/equatorial.py @@ -22,7 +22,7 @@ from .utils import DEFAULT_OBSTIME, EARTH_CENTER -__all__ = ['TEME', 'TETE'] +__all__ = ["TEME", "TETE"] doc_footer_teme = """ Other parameters @@ -82,6 +82,7 @@ class TETE(BaseRADecFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) location = EarthLocationAttribute(default=EARTH_CENTER) + # Self transform goes through ICRS and is defined in icrs_cirs_transforms.py @@ -107,5 +108,6 @@ class TEME(BaseCoordinateFrame): obstime = TimeAttribute() + # Transformation functions for getting to/from TEME and ITRS are in # intermediate rotation transforms.py diff --git a/astropy/coordinates/builtin_frames/fk4.py b/astropy/coordinates/builtin_frames/fk4.py index 1e013e41469..4cdbefecd0f 100644 --- a/astropy/coordinates/builtin_frames/fk4.py +++ b/astropy/coordinates/builtin_frames/fk4.py @@ -19,7 +19,7 @@ from .baseradec import BaseRADecFrame, doc_components from .utils import EQUINOX_B1950 -__all__ = ['FK4', 'FK4NoETerms'] +__all__ = ["FK4", "FK4NoETerms"] doc_footer_fk4 = """ @@ -45,7 +45,7 @@ class FK4(BaseRADecFrame): """ equinox = TimeAttribute(default=EQUINOX_B1950) - obstime = TimeAttribute(default=None, secondary_attribute='equinox') + obstime = TimeAttribute(default=None, secondary_attribute="equinox") # the "self" transform @@ -70,7 +70,7 @@ class FK4NoETerms(BaseRADecFrame): """ equinox = TimeAttribute(default=EQUINOX_B1950) - obstime = TimeAttribute(default=None, secondary_attribute='equinox') + obstime = TimeAttribute(default=None, secondary_attribute="equinox") @staticmethod def _precession_matrix(oldequinox, newequinox): @@ -131,12 +131,16 @@ def fk4_e_terms(equinox): o = earth.obliquity(equinox.jd, algorithm=1980) o = np.radians(o) - return (e * k * np.sin(g), - -e * k * np.cos(g) * np.cos(o), - -e * k * np.cos(g) * np.sin(o)) + return ( + e * k * np.sin(g), + -e * k * np.cos(g) * np.cos(o), + -e * k * np.cos(g) * np.sin(o), + ) -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, FK4, FK4NoETerms) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, FK4, FK4NoETerms +) def fk4_to_fk4_no_e(fk4coord, fk4noeframe): # Extract cartesian vector rep = fk4coord.cartesian @@ -149,8 +153,9 @@ def fk4_to_fk4_no_e(fk4coord, fk4noeframe): # the observing time/epoch) of the coordinates. See issue #1496 for a # discussion of this. eterms_a = CartesianRepresentation( - u.Quantity(fk4_e_terms(fk4coord.equinox), u.dimensionless_unscaled, - copy=False), copy=False) + u.Quantity(fk4_e_terms(fk4coord.equinox), u.dimensionless_unscaled, copy=False), + copy=False, + ) rep = rep - eterms_a + eterms_a.dot(rep) * rep # Find new distance (for re-normalization) @@ -164,7 +169,9 @@ def fk4_to_fk4_no_e(fk4coord, fk4noeframe): rep = rep.represent_as(UnitSphericalRepresentation) # if no obstime was given in the new frame, use the old one for consistency - newobstime = fk4coord._obstime if fk4noeframe._obstime is None else fk4noeframe._obstime + newobstime = ( + fk4coord._obstime if fk4noeframe._obstime is None else fk4noeframe._obstime + ) fk4noe = FK4NoETerms(rep, equinox=fk4coord.equinox, obstime=newobstime) if fk4coord.equinox != fk4noeframe.equinox: @@ -173,12 +180,15 @@ def fk4_to_fk4_no_e(fk4coord, fk4noeframe): return fk4noe -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, FK4NoETerms, FK4) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, FK4NoETerms, FK4 +) def fk4_no_e_to_fk4(fk4noecoord, fk4frame): # first precess, if necessary if fk4noecoord.equinox != fk4frame.equinox: - fk4noe_w_fk4equinox = FK4NoETerms(equinox=fk4frame.equinox, - obstime=fk4noecoord.obstime) + fk4noe_w_fk4equinox = FK4NoETerms( + equinox=fk4frame.equinox, obstime=fk4noecoord.obstime + ) fk4noecoord = fk4noecoord.transform_to(fk4noe_w_fk4equinox) # Extract cartesian vector @@ -192,12 +202,15 @@ def fk4_no_e_to_fk4(fk4noecoord, fk4frame): # the observing time/epoch) of the coordinates. See issue #1496 for a # discussion of this. eterms_a = CartesianRepresentation( - u.Quantity(fk4_e_terms(fk4noecoord.equinox), u.dimensionless_unscaled, - copy=False), copy=False) + u.Quantity( + fk4_e_terms(fk4noecoord.equinox), u.dimensionless_unscaled, copy=False + ), + copy=False, + ) rep0 = rep.copy() for _ in range(10): - rep = (eterms_a + rep0) / (1. + eterms_a.dot(rep)) + rep = (eterms_a + rep0) / (1.0 + eterms_a.dot(rep)) # Find new distance (for re-normalization) d_new = rep.norm() diff --git a/astropy/coordinates/builtin_frames/fk4_fk5_transforms.py b/astropy/coordinates/builtin_frames/fk4_fk5_transforms.py index 169eced23a8..769545d40db 100644 --- a/astropy/coordinates/builtin_frames/fk4_fk5_transforms.py +++ b/astropy/coordinates/builtin_frames/fk4_fk5_transforms.py @@ -14,14 +14,23 @@ # FK5 to/from FK4 -------------------> # B1950->J2000 matrix from Murray 1989 A&A 218,325 eqn 28 _B1950_TO_J2000_M = np.array( - [[0.9999256794956877, -0.0111814832204662, -0.0048590038153592], - [0.0111814832391717, 0.9999374848933135, -0.0000271625947142], - [0.0048590037723143, -0.0000271702937440, 0.9999881946023742]]) - -_FK4_CORR = np.array( - [[-0.0026455262, -1.1539918689, +2.1111346190], - [+1.1540628161, -0.0129042997, +0.0236021478], - [-2.1112979048, -0.0056024448, +0.0102587734]]) * 1.e-6 + [ + [0.9999256794956877, -0.0111814832204662, -0.0048590038153592], + [0.0111814832391717, +0.9999374848933135, -0.0000271625947142], + [0.0048590037723143, -0.0000271702937440, +0.9999881946023742], + ] +) + +_FK4_CORR = ( + np.array( + [ + [-0.0026455262, -1.1539918689, +2.1111346190], + [+1.1540628161, -0.0129042997, +0.0236021478], + [-2.1112979048, -0.0056024448, +0.0102587734], + ] + ) + * 1.0e-6 +) def _fk4_B_matrix(obstime): @@ -30,8 +39,8 @@ def _fk4_B_matrix(obstime): rotating system - see Murray 89 eqn 29 """ # Note this is *julian century*, not besselian - T = (obstime.jyear - 1950.) / 100. - if getattr(T, 'shape', ()): + T = (obstime.jyear - 1950.0) / 100.0 + if getattr(T, "shape", ()): # Ensure we broadcast possibly arrays of times properly. T.shape += (1, 1) return _B1950_TO_J2000_M + _FK4_CORR * T diff --git a/astropy/coordinates/builtin_frames/fk5.py b/astropy/coordinates/builtin_frames/fk5.py index 93d47cad465..b5e75144251 100644 --- a/astropy/coordinates/builtin_frames/fk5.py +++ b/astropy/coordinates/builtin_frames/fk5.py @@ -9,7 +9,7 @@ from .baseradec import BaseRADecFrame, doc_components from .utils import EQUINOX_J2000 -__all__ = ['FK5'] +__all__ = ["FK5"] doc_footer = """ diff --git a/astropy/coordinates/builtin_frames/galactic.py b/astropy/coordinates/builtin_frames/galactic.py index 84e9ea14ad2..99f04f96697 100644 --- a/astropy/coordinates/builtin_frames/galactic.py +++ b/astropy/coordinates/builtin_frames/galactic.py @@ -15,7 +15,7 @@ # these are needed for defining the NGP from .fk5 import FK5 -__all__ = ['Galactic'] +__all__ = ["Galactic"] doc_components = """ @@ -63,19 +63,19 @@ class Galactic(BaseCoordinateFrame): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'l'), - RepresentationMapping('lat', 'b') + RepresentationMapping("lon", "l"), + RepresentationMapping("lat", "b"), ], r.CartesianRepresentation: [ - RepresentationMapping('x', 'u'), - RepresentationMapping('y', 'v'), - RepresentationMapping('z', 'w') + RepresentationMapping("x", "u"), + RepresentationMapping("y", "v"), + RepresentationMapping("z", "w"), ], r.CartesianDifferential: [ - RepresentationMapping('d_x', 'U', u.km/u.s), - RepresentationMapping('d_y', 'V', u.km/u.s), - RepresentationMapping('d_z', 'W', u.km/u.s) - ] + RepresentationMapping("d_x", "U", u.km / u.s), + RepresentationMapping("d_y", "V", u.km / u.s), + RepresentationMapping("d_z", "W", u.km / u.s), + ], } default_representation = r.SphericalRepresentation @@ -85,7 +85,7 @@ class Galactic(BaseCoordinateFrame): # transformations to/from FK4/5 # These are from the IAU's definition of galactic coordinates - _ngp_B1950 = FK4NoETerms(ra=192.25*u.degree, dec=27.4*u.degree) + _ngp_B1950 = FK4NoETerms(ra=192.25 * u.degree, dec=27.4 * u.degree) _lon0_B1950 = Angle(123, u.degree) # These are *not* from Reid & Brunthaler 2004 - instead, they were @@ -97,5 +97,5 @@ class Galactic(BaseCoordinateFrame): # from Reid & Brunthaler 2004 and the best self-consistency between FK5 # -> Galactic and FK5 -> FK4 -> Galactic. The lon0 angle was found by # optimizing the self-consistency. - _ngp_J2000 = FK5(ra=192.8594812065348*u.degree, dec=27.12825118085622*u.degree) + _ngp_J2000 = FK5(ra=192.8594812065348 * u.degree, dec=27.12825118085622 * u.degree) _lon0_J2000 = Angle(122.9319185680026, u.degree) diff --git a/astropy/coordinates/builtin_frames/galactic_transforms.py b/astropy/coordinates/builtin_frames/galactic_transforms.py index a9d4fb44e7f..24a079b3f65 100644 --- a/astropy/coordinates/builtin_frames/galactic_transforms.py +++ b/astropy/coordinates/builtin_frames/galactic_transforms.py @@ -16,9 +16,9 @@ def fk5_to_gal(fk5coord, galframe): # need precess to J2000 first return ( - rotation_matrix(180 - Galactic._lon0_J2000.degree, 'z') - @ rotation_matrix(90 - Galactic._ngp_J2000.dec.degree, 'y') - @ rotation_matrix(Galactic._ngp_J2000.ra.degree, 'z') + rotation_matrix(180 - Galactic._lon0_J2000.degree, "z") + @ rotation_matrix(90 - Galactic._ngp_J2000.dec.degree, "y") + @ rotation_matrix(Galactic._ngp_J2000.ra.degree, "z") @ fk5coord._precession_matrix(fk5coord.equinox, EQUINOX_J2000) ) @@ -31,9 +31,9 @@ def _gal_to_fk5(galcoord, fk5frame): @frame_transform_graph.transform(DynamicMatrixTransform, FK4NoETerms, Galactic) def fk4_to_gal(fk4coords, galframe): return ( - rotation_matrix(180 - Galactic._lon0_B1950.degree, 'z') - @ rotation_matrix(90 - Galactic._ngp_B1950.dec.degree, 'y') - @ rotation_matrix(Galactic._ngp_B1950.ra.degree, 'z') + rotation_matrix(180 - Galactic._lon0_B1950.degree, "z") + @ rotation_matrix(90 - Galactic._ngp_B1950.dec.degree, "y") + @ rotation_matrix(Galactic._ngp_B1950.ra.degree, "z") @ fk4coords._precession_matrix(fk4coords.equinox, EQUINOX_B1950) ) diff --git a/astropy/coordinates/builtin_frames/galactocentric.py b/astropy/coordinates/builtin_frames/galactocentric.py index 1f99bb6dc3f..e530bd2620c 100644 --- a/astropy/coordinates/builtin_frames/galactocentric.py +++ b/astropy/coordinates/builtin_frames/galactocentric.py @@ -27,14 +27,14 @@ from .icrs import ICRS -__all__ = ['Galactocentric'] +__all__ = ["Galactocentric"] # Measured by minimizing the difference between a plane of coordinates along # l=0, b=[-90,90] and the Galactocentric x-z plane # This is not used directly, but accessed via `get_roll0`. We define it here to # prevent having to create new Angle objects every time `get_roll0` is called. -_ROLL0 = Angle(58.5986320306*u.degree) +_ROLL0 = Angle(58.5986320306 * u.degree) class _StateProxy(MappingView): @@ -146,7 +146,7 @@ class galactocentric_frame_defaults(ScienceState): """ - _latest_value = 'v4.0' + _latest_value = "v4.0" _value = None _references = None _state = dict() # all other data @@ -169,8 +169,12 @@ class galactocentric_frame_defaults(ScienceState): ), "references": _StateProxy( { - "galcen_coord": "https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R", - "galcen_distance": "https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G", + "galcen_coord": ( + "https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R" + ), + "galcen_distance": ( + "https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G" + ), "galcen_v_sun": [ "https://ui.adsabs.harvard.edu/abs/2018RNAAS...2..210D", "https://ui.adsabs.harvard.edu/abs/2018A%26A...615L..15G", @@ -197,8 +201,12 @@ class galactocentric_frame_defaults(ScienceState): ), "references": _StateProxy( { - "galcen_coord": "https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R", - "galcen_distance": "https://ui.adsabs.harvard.edu/#abs/2009ApJ...692.1075G", + "galcen_coord": ( + "https://ui.adsabs.harvard.edu/abs/2004ApJ...616..872R" + ), + "galcen_distance": ( + "https://ui.adsabs.harvard.edu/#abs/2009ApJ...692.1075G" + ), "galcen_v_sun": [ "https://ui.adsabs.harvard.edu/#abs/2010MNRAS.403.1829S", "https://ui.adsabs.harvard.edu/#abs/2015ApJS..216...29B", @@ -246,7 +254,7 @@ def get_from_registry(cls, name: str): """ # Resolve the meaning of 'latest': latest parameter set is from v4.0 # - update this as newer parameter choices are added - if name == 'latest': + if name == "latest": name = cls._latest_value # Get the state from the registry. @@ -298,19 +306,18 @@ def validate(cls, value): for k in value.frame_attributes: parameters[k] = getattr(value, k) cls._references = value.frame_attribute_references.copy() - cls._state = dict(parameters=parameters, - references=cls._references) + cls._state = dict(parameters=parameters, references=cls._references) else: - raise ValueError("Invalid input to retrieve solar parameters for " - "Galactocentric frame: input must be a string, " - "dict, or Galactocentric instance") + raise ValueError( + "Invalid input to retrieve solar parameters for Galactocentric frame:" + " input must be a string, dict, or Galactocentric instance" + ) return parameters @classmethod - def register(cls, name: str, parameters: dict, references=None, - **meta: dict): + def register(cls, name: str, parameters: dict, references=None, **meta: dict): """Register a set of parameters. Parameters @@ -327,8 +334,7 @@ def register(cls, name: str, parameters: dict, references=None, """ # check on contents of `parameters` - must_have = {"galcen_coord", "galcen_distance", "galcen_v_sun", - "z_sun", "roll"} + must_have = {"galcen_coord", "galcen_distance", "galcen_v_sun", "z_sun", "roll"} missing = must_have.difference(parameters) if missing: raise ValueError(f"Missing parameters: {missing}") @@ -477,8 +483,7 @@ class Galactocentric(BaseCoordinateFrame): galcen_coord = CoordinateAttribute(frame=ICRS) galcen_distance = QuantityAttribute(unit=u.kpc) - galcen_v_sun = DifferentialAttribute( - allowed_classes=[r.CartesianDifferential]) + galcen_v_sun = DifferentialAttribute(allowed_classes=[r.CartesianDifferential]) z_sun = QuantityAttribute(unit=u.pc) roll = QuantityAttribute(unit=u.deg) @@ -487,8 +492,9 @@ def __init__(self, *args, **kwargs): # Set default frame attribute values based on the ScienceState instance # for the solar parameters defined above default_params = galactocentric_frame_defaults.get() - self.frame_attribute_references = \ + self.frame_attribute_references = ( galactocentric_frame_defaults.references.copy() + ) for k in default_params: if k in kwargs: @@ -514,6 +520,7 @@ def get_roll0(cls): # API, so it's better for it to be accessible from Galactocentric return _ROLL0 + # ICRS to/from Galactocentric -----------------------> @@ -526,19 +533,19 @@ def get_matrix_vectors(galactocentric_frame, inverse=False): gcf = galactocentric_frame # rotation matrix to align x(ICRS) with the vector to the Galactic center - mat1 = rotation_matrix(-gcf.galcen_coord.dec, 'y') - mat2 = rotation_matrix(gcf.galcen_coord.ra, 'z') + mat1 = rotation_matrix(-gcf.galcen_coord.dec, "y") + mat2 = rotation_matrix(gcf.galcen_coord.ra, "z") # extra roll away from the Galactic x-z plane - mat0 = rotation_matrix(gcf.get_roll0() - gcf.roll, 'x') + mat0 = rotation_matrix(gcf.get_roll0() - gcf.roll, "x") # construct transformation matrix and use it R = mat0 @ mat1 @ mat2 # Now need to translate by Sun-Galactic center distance around x' and # rotate about y' to account for tilt due to Sun's height above the plane - translation = r.CartesianRepresentation(gcf.galcen_distance * [1., 0., 0.]) + translation = r.CartesianRepresentation(gcf.galcen_distance * [1.0, 0.0, 0.0]) z_d = gcf.z_sun / gcf.galcen_distance - H = rotation_matrix(-np.arcsin(z_d), 'y') + H = rotation_matrix(-np.arcsin(z_d), "y") # compute total matrices A = H @ R @@ -553,7 +560,8 @@ def get_matrix_vectors(galactocentric_frame, inverse=False): A = matrix_transpose(A) offset = (-offset).transform(A) offset_v = r.CartesianDifferential.from_cartesian( - (-gcf.galcen_v_sun).to_cartesian().transform(A)) + (-gcf.galcen_v_sun).to_cartesian().transform(A) + ) offset = offset.with_differentials(offset_v) else: @@ -564,18 +572,23 @@ def get_matrix_vectors(galactocentric_frame, inverse=False): def _check_coord_repr_diff_types(c): if isinstance(c.data, r.UnitSphericalRepresentation): - raise ConvertError("Transforming to/from a Galactocentric frame " - "requires a 3D coordinate, e.g. (angle, angle, " - "distance) or (x, y, z).") - - if ('s' in c.data.differentials and - isinstance(c.data.differentials['s'], - (r.UnitSphericalDifferential, - r.UnitSphericalCosLatDifferential, - r.RadialDifferential))): - raise ConvertError("Transforming to/from a Galactocentric frame " - "requires a 3D velocity, e.g., proper motion " - "components and radial velocity.") + raise ConvertError( + "Transforming to/from a Galactocentric frame requires a 3D coordinate, e.g." + " (angle, angle, distance) or (x, y, z)." + ) + + if "s" in c.data.differentials and isinstance( + c.data.differentials["s"], + ( + r.UnitSphericalDifferential, + r.UnitSphericalCosLatDifferential, + r.RadialDifferential, + ), + ): + raise ConvertError( + "Transforming to/from a Galactocentric frame requires a 3D velocity, e.g.," + " proper motion components and radial velocity." + ) @frame_transform_graph.transform(AffineTransform, ICRS, Galactocentric) diff --git a/astropy/coordinates/builtin_frames/gcrs.py b/astropy/coordinates/builtin_frames/gcrs.py index f9d1b455dd1..35dbcd07d57 100644 --- a/astropy/coordinates/builtin_frames/gcrs.py +++ b/astropy/coordinates/builtin_frames/gcrs.py @@ -11,7 +11,7 @@ from .baseradec import BaseRADecFrame, doc_components from .utils import DEFAULT_OBSTIME, EQUINOX_J2000 -__all__ = ['GCRS', 'PrecessedGeocentric'] +__all__ = ["GCRS", "PrecessedGeocentric"] doc_footer_gcrs = """ @@ -55,10 +55,8 @@ class GCRS(BaseRADecFrame): """ obstime = TimeAttribute(default=DEFAULT_OBSTIME) - obsgeoloc = CartesianRepresentationAttribute(default=[0, 0, 0], - unit=u.m) - obsgeovel = CartesianRepresentationAttribute(default=[0, 0, 0], - unit=u.m/u.s) + obsgeoloc = CartesianRepresentationAttribute(default=[0, 0, 0], unit=u.m) + obsgeovel = CartesianRepresentationAttribute(default=[0, 0, 0], unit=u.m / u.s) # The "self-transform" is defined in icrs_cirs_transformations.py, because in @@ -103,4 +101,4 @@ class PrecessedGeocentric(BaseRADecFrame): equinox = TimeAttribute(default=EQUINOX_J2000) obstime = TimeAttribute(default=DEFAULT_OBSTIME) obsgeoloc = CartesianRepresentationAttribute(default=[0, 0, 0], unit=u.m) - obsgeovel = CartesianRepresentationAttribute(default=[0, 0, 0], unit=u.m/u.s) + obsgeovel = CartesianRepresentationAttribute(default=[0, 0, 0], unit=u.m / u.s) diff --git a/astropy/coordinates/builtin_frames/hadec.py b/astropy/coordinates/builtin_frames/hadec.py index 288a65ce43c..3a20b90358f 100644 --- a/astropy/coordinates/builtin_frames/hadec.py +++ b/astropy/coordinates/builtin_frames/hadec.py @@ -14,7 +14,7 @@ ) from astropy.utils.decorators import format_doc -__all__ = ['HADec'] +__all__ = ["HADec"] doc_components = """ @@ -91,8 +91,8 @@ class HADec(BaseCoordinateFrame): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'ha', u.hourangle), - RepresentationMapping('lat', 'dec') + RepresentationMapping("lon", "ha", u.hourangle), + RepresentationMapping("lat", "dec"), ] } @@ -104,7 +104,7 @@ class HADec(BaseCoordinateFrame): pressure = QuantityAttribute(default=0, unit=u.hPa) temperature = QuantityAttribute(default=0, unit=u.deg_C) relative_humidity = QuantityAttribute(default=0, unit=u.dimensionless_unscaled) - obswl = QuantityAttribute(default=1*u.micron, unit=u.micron) + obswl = QuantityAttribute(default=1 * u.micron, unit=u.micron) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -113,11 +113,11 @@ def __init__(self, *args, **kwargs): @staticmethod def _set_data_lon_wrap_angle(data): - if hasattr(data, 'lon'): - data.lon.wrap_angle = 180. * u.deg + if hasattr(data, "lon"): + data.lon.wrap_angle = 180.0 * u.deg return data - def represent_as(self, base, s='base', in_frame_units=False): + def represent_as(self, base, s="base", in_frame_units=False): """ Ensure the wrap angle for any spherical representations. diff --git a/astropy/coordinates/builtin_frames/hcrs.py b/astropy/coordinates/builtin_frames/hcrs.py index a87d4206574..e8f381c0302 100644 --- a/astropy/coordinates/builtin_frames/hcrs.py +++ b/astropy/coordinates/builtin_frames/hcrs.py @@ -7,7 +7,7 @@ from .baseradec import BaseRADecFrame, doc_components from .utils import DEFAULT_OBSTIME -__all__ = ['HCRS'] +__all__ = ["HCRS"] doc_footer = """ @@ -42,4 +42,5 @@ class HCRS(BaseRADecFrame): obstime = TimeAttribute(default=DEFAULT_OBSTIME) + # Transformations are defined in icrs_circ_transforms.py diff --git a/astropy/coordinates/builtin_frames/icrs.py b/astropy/coordinates/builtin_frames/icrs.py index 9a1929238d4..78009d6a447 100644 --- a/astropy/coordinates/builtin_frames/icrs.py +++ b/astropy/coordinates/builtin_frames/icrs.py @@ -5,7 +5,7 @@ from .baseradec import BaseRADecFrame, doc_components -__all__ = ['ICRS'] +__all__ = ["ICRS"] @format_doc(base_doc, components=doc_components, footer="") diff --git a/astropy/coordinates/builtin_frames/icrs_cirs_transforms.py b/astropy/coordinates/builtin_frames/icrs_cirs_transforms.py index cb6d6a25196..ccb583d1015 100644 --- a/astropy/coordinates/builtin_frames/icrs_cirs_transforms.py +++ b/astropy/coordinates/builtin_frames/icrs_cirs_transforms.py @@ -32,28 +32,37 @@ def icrs_to_cirs(icrs_coo, cirs_frame): # first set up the astrometry context for ICRS<->CIRS astrom = erfa_astrom.get().apco(cirs_frame) - if icrs_coo.data.get_name() == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one: + if ( + icrs_coo.data.get_name() == "unitspherical" + or icrs_coo.data.to_cartesian().x.unit == u.one + ): # if no distance, just do the infinite-distance/no parallax calculation srepr = icrs_coo.spherical cirs_ra, cirs_dec = atciqz(srepr.without_differentials(), astrom) - newrep = UnitSphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), - lon=u.Quantity(cirs_ra, u.radian, copy=False), - copy=False) + newrep = UnitSphericalRepresentation( + lat=u.Quantity(cirs_dec, u.radian, copy=False), + lon=u.Quantity(cirs_ra, u.radian, copy=False), + copy=False, + ) else: # When there is a distance, we first offset for parallax to get the # astrometric coordinate direction and *then* run the ERFA transform for # no parallax/PM. This ensures reversibility and is more sensible for # inside solar system objects - astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, - xyz_axis=-1, copy=False) + astrom_eb = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) newcart = icrs_coo.cartesian - astrom_eb srepr = newcart.represent_as(SphericalRepresentation) cirs_ra, cirs_dec = atciqz(srepr.without_differentials(), astrom) - newrep = SphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False), - lon=u.Quantity(cirs_ra, u.radian, copy=False), - distance=srepr.distance, copy=False) + newrep = SphericalRepresentation( + lat=u.Quantity(cirs_dec, u.radian, copy=False), + lon=u.Quantity(cirs_ra, u.radian, copy=False), + distance=srepr.distance, + copy=False, + ) return cirs_frame.realize_frame(newrep) @@ -66,25 +75,33 @@ def cirs_to_icrs(cirs_coo, icrs_frame): srepr = cirs_coo.represent_as(SphericalRepresentation) i_ra, i_dec = aticq(srepr.without_differentials(), astrom) - if cirs_coo.data.get_name() == 'unitspherical' or cirs_coo.data.to_cartesian().x.unit == u.one: + if ( + cirs_coo.data.get_name() == "unitspherical" + or cirs_coo.data.to_cartesian().x.unit == u.one + ): # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer - newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), - lon=u.Quantity(i_ra, u.radian, copy=False), - copy=False) + newrep = UnitSphericalRepresentation( + lat=u.Quantity(i_dec, u.radian, copy=False), + lon=u.Quantity(i_ra, u.radian, copy=False), + copy=False, + ) else: # When there is a distance, apply the parallax/offset to the SSB as the # last step - ensures round-tripping with the icrs_to_cirs transform # the distance in intermedrep is *not* a real distance as it does not # include the offset back to the SSB - intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), - lon=u.Quantity(i_ra, u.radian, copy=False), - distance=srepr.distance, - copy=False) - - astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, - xyz_axis=-1, copy=False) + intermedrep = SphericalRepresentation( + lat=u.Quantity(i_dec, u.radian, copy=False), + lon=u.Quantity(i_ra, u.radian, copy=False), + distance=srepr.distance, + copy=False, + ) + + astrom_eb = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) newrep = intermedrep + astrom_eb return icrs_frame.realize_frame(newrep) @@ -92,40 +109,49 @@ def cirs_to_icrs(cirs_coo, icrs_frame): # Now the GCRS-related transforms to/from ICRS + @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, ICRS, GCRS) def icrs_to_gcrs(icrs_coo, gcrs_frame): # first set up the astrometry context for ICRS<->GCRS. astrom = erfa_astrom.get().apcs(gcrs_frame) - if icrs_coo.data.get_name() == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one: + if ( + icrs_coo.data.get_name() == "unitspherical" + or icrs_coo.data.to_cartesian().x.unit == u.one + ): # if no distance, just do the infinite-distance/no parallax calculation srepr = icrs_coo.represent_as(SphericalRepresentation) gcrs_ra, gcrs_dec = atciqz(srepr.without_differentials(), astrom) - newrep = UnitSphericalRepresentation(lat=u.Quantity(gcrs_dec, u.radian, copy=False), - lon=u.Quantity(gcrs_ra, u.radian, copy=False), - copy=False) + newrep = UnitSphericalRepresentation( + lat=u.Quantity(gcrs_dec, u.radian, copy=False), + lon=u.Quantity(gcrs_ra, u.radian, copy=False), + copy=False, + ) else: # When there is a distance, we first offset for parallax to get the # BCRS coordinate direction and *then* run the ERFA transform for no # parallax/PM. This ensures reversibility and is more sensible for # inside solar system objects - astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, - xyz_axis=-1, copy=False) + astrom_eb = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) newcart = icrs_coo.cartesian - astrom_eb srepr = newcart.represent_as(SphericalRepresentation) gcrs_ra, gcrs_dec = atciqz(srepr.without_differentials(), astrom) - newrep = SphericalRepresentation(lat=u.Quantity(gcrs_dec, u.radian, copy=False), - lon=u.Quantity(gcrs_ra, u.radian, copy=False), - distance=srepr.distance, copy=False) + newrep = SphericalRepresentation( + lat=u.Quantity(gcrs_dec, u.radian, copy=False), + lon=u.Quantity(gcrs_ra, u.radian, copy=False), + distance=srepr.distance, + copy=False, + ) return gcrs_frame.realize_frame(newrep) -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - GCRS, ICRS) +@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GCRS, ICRS) def gcrs_to_icrs(gcrs_coo, icrs_frame): # set up the astrometry context for ICRS<->GCRS and then convert to BCRS # coordinate direction @@ -134,25 +160,33 @@ def gcrs_to_icrs(gcrs_coo, icrs_frame): srepr = gcrs_coo.represent_as(SphericalRepresentation) i_ra, i_dec = aticq(srepr.without_differentials(), astrom) - if gcrs_coo.data.get_name() == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one: + if ( + gcrs_coo.data.get_name() == "unitspherical" + or gcrs_coo.data.to_cartesian().x.unit == u.one + ): # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer - newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), - lon=u.Quantity(i_ra, u.radian, copy=False), - copy=False) + newrep = UnitSphericalRepresentation( + lat=u.Quantity(i_dec, u.radian, copy=False), + lon=u.Quantity(i_ra, u.radian, copy=False), + copy=False, + ) else: # When there is a distance, apply the parallax/offset to the SSB as the # last step - ensures round-tripping with the icrs_to_gcrs transform # the distance in intermedrep is *not* a real distance as it does not # include the offset back to the SSB - intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False), - lon=u.Quantity(i_ra, u.radian, copy=False), - distance=srepr.distance, - copy=False) - - astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au, - xyz_axis=-1, copy=False) + intermedrep = SphericalRepresentation( + lat=u.Quantity(i_dec, u.radian, copy=False), + lon=u.Quantity(i_ra, u.radian, copy=False), + distance=srepr.distance, + copy=False, + ) + + astrom_eb = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) newrep = intermedrep + astrom_eb return icrs_frame.realize_frame(newrep) @@ -160,12 +194,11 @@ def gcrs_to_icrs(gcrs_coo, icrs_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GCRS, HCRS) def gcrs_to_hcrs(gcrs_coo, hcrs_frame): - if np.any(gcrs_coo.obstime != hcrs_frame.obstime): # if they GCRS obstime and HCRS obstime are not the same, we first # have to move to a GCRS where they are. frameattrs = gcrs_coo.get_frame_attr_defaults() - frameattrs['obstime'] = hcrs_frame.obstime + frameattrs["obstime"] = hcrs_frame.obstime gcrs_coo = gcrs_coo.transform_to(GCRS(**frameattrs)) # set up the astrometry context for ICRS<->GCRS and then convert to ICRS @@ -177,7 +210,10 @@ def gcrs_to_hcrs(gcrs_coo, hcrs_frame): # convert to Quantity objects i_ra = u.Quantity(i_ra, u.radian, copy=False) i_dec = u.Quantity(i_dec, u.radian, copy=False) - if gcrs_coo.data.get_name() == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one: + if ( + gcrs_coo.data.get_name() == "unitspherical" + or gcrs_coo.data.to_cartesian().x.unit == u.one + ): # if no distance, just use the coordinate direction to yield the # infinite-distance/no parallax answer newrep = UnitSphericalRepresentation(lat=i_dec, lon=i_ra, copy=False) @@ -188,16 +224,16 @@ def gcrs_to_hcrs(gcrs_coo, hcrs_frame): # Note that the distance in intermedrep is *not* a real distance as it # does not include the offset back to the Heliocentre - intermedrep = SphericalRepresentation(lat=i_dec, lon=i_ra, - distance=srepr.distance, - copy=False) + intermedrep = SphericalRepresentation( + lat=i_dec, lon=i_ra, distance=srepr.distance, copy=False + ) # astrom['eh'] and astrom['em'] contain Sun to observer unit vector, # and distance, respectively. Shapes are (X) and (X,3), where (X) is the # shape resulting from broadcasting the shape of the times object # against the shape of the pv array. # broadcast em to eh and scale eh - eh = astrom['eh'] * astrom['em'][..., np.newaxis] + eh = astrom["eh"] * astrom["em"][..., np.newaxis] eh = CartesianRepresentation(eh, unit=u.au, xyz_axis=-1, copy=False) newrep = intermedrep.to_cartesian() + eh @@ -205,10 +241,11 @@ def gcrs_to_hcrs(gcrs_coo, hcrs_frame): return hcrs_frame.realize_frame(newrep) -_NEED_ORIGIN_HINT = ("The input {0} coordinates do not have length units. This " - "probably means you created coordinates with lat/lon but " - "no distance. Heliocentric<->ICRS transforms cannot " - "function in this case because there is an origin shift.") +_NEED_ORIGIN_HINT = ( + "The input {0} coordinates do not have length units. This probably means you" + " created coordinates with lat/lon but no distance. Heliocentric<->ICRS transforms" + " cannot function in this case because there is an origin shift." +) @frame_transform_graph.transform(AffineTransform, HCRS, ICRS) @@ -217,8 +254,9 @@ def hcrs_to_icrs(hcrs_coo, icrs_frame): if isinstance(hcrs_coo.data, UnitSphericalRepresentation): raise u.UnitsError(_NEED_ORIGIN_HINT.format(hcrs_coo.__class__.__name__)) - return None, get_offset_sun_from_barycenter(hcrs_coo.obstime, - include_velocity=bool(hcrs_coo.data.differentials)) + return None, get_offset_sun_from_barycenter( + hcrs_coo.obstime, include_velocity=bool(hcrs_coo.data.differentials) + ) @frame_transform_graph.transform(AffineTransform, ICRS, HCRS) @@ -227,8 +265,11 @@ def icrs_to_hcrs(icrs_coo, hcrs_frame): if isinstance(icrs_coo.data, UnitSphericalRepresentation): raise u.UnitsError(_NEED_ORIGIN_HINT.format(icrs_coo.__class__.__name__)) - return None, get_offset_sun_from_barycenter(hcrs_frame.obstime, reverse=True, - include_velocity=bool(icrs_coo.data.differentials)) + return None, get_offset_sun_from_barycenter( + hcrs_frame.obstime, + reverse=True, + include_velocity=bool(icrs_coo.data.differentials), + ) # Create loopback transformations diff --git a/astropy/coordinates/builtin_frames/icrs_fk5_transforms.py b/astropy/coordinates/builtin_frames/icrs_fk5_transforms.py index 3bf40fb1263..f95f2aff1ed 100644 --- a/astropy/coordinates/builtin_frames/icrs_fk5_transforms.py +++ b/astropy/coordinates/builtin_frames/icrs_fk5_transforms.py @@ -15,14 +15,14 @@ def _icrs_to_fk5_matrix(): functions. """ - eta0 = -19.9 / 3600000. - xi0 = 9.1 / 3600000. - da0 = -22.9 / 3600000. + eta0 = -19.9 / 3600000.0 + xi0 = 9.1 / 3600000.0 + da0 = -22.9 / 3600000.0 return ( - rotation_matrix(-eta0, 'x') - @ rotation_matrix(xi0, 'y') - @ rotation_matrix(da0, 'z') + rotation_matrix(-eta0, "x") + @ rotation_matrix(xi0, "y") + @ rotation_matrix(da0, "z") ) diff --git a/astropy/coordinates/builtin_frames/icrs_observed_transforms.py b/astropy/coordinates/builtin_frames/icrs_observed_transforms.py index 07bed32c3f6..c5bdc671dad 100644 --- a/astropy/coordinates/builtin_frames/icrs_observed_transforms.py +++ b/astropy/coordinates/builtin_frames/icrs_observed_transforms.py @@ -25,8 +25,10 @@ @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, ICRS, HADec) def icrs_to_observed(icrs_coo, observed_frame): # if the data are UnitSphericalRepresentation, we can skip the distance calculations - is_unitspherical = (isinstance(icrs_coo.data, UnitSphericalRepresentation) or - icrs_coo.cartesian.x.unit == u.one) + is_unitspherical = ( + isinstance(icrs_coo.data, UnitSphericalRepresentation) + or icrs_coo.cartesian.x.unit == u.one + ) # first set up the astrometry context for ICRS<->observed astrom = erfa_astrom.get().apco(observed_frame) @@ -34,9 +36,12 @@ def icrs_to_observed(icrs_coo, observed_frame): if is_unitspherical: srepr = icrs_coo.spherical else: - observer_icrs = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) + observer_icrs = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) srepr = (icrs_coo.cartesian - observer_icrs).represent_as( - SphericalRepresentation) + SphericalRepresentation + ) # convert to topocentric CIRS cirs_ra, cirs_dec = atciqz(srepr, astrom) @@ -49,9 +54,13 @@ def icrs_to_observed(icrs_coo, observed_frame): _, _, lon, lat, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) if is_unitspherical: - obs_srepr = UnitSphericalRepresentation(lon << u.radian, lat << u.radian, copy=False) + obs_srepr = UnitSphericalRepresentation( + lon << u.radian, lat << u.radian, copy=False + ) else: - obs_srepr = SphericalRepresentation(lon << u.radian, lat << u.radian, srepr.distance, copy=False) + obs_srepr = SphericalRepresentation( + lon << u.radian, lat << u.radian, srepr.distance, copy=False + ) return observed_frame.realize_frame(obs_srepr) @@ -59,8 +68,10 @@ def icrs_to_observed(icrs_coo, observed_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HADec, ICRS) def observed_to_icrs(observed_coo, icrs_frame): # if the data are UnitSphericalRepresentation, we can skip the distance calculations - is_unitspherical = (isinstance(observed_coo.data, UnitSphericalRepresentation) or - observed_coo.cartesian.x.unit == u.one) + is_unitspherical = ( + isinstance(observed_coo.data, UnitSphericalRepresentation) + or observed_coo.cartesian.x.unit == u.one + ) usrepr = observed_coo.represent_as(UnitSphericalRepresentation) lon = usrepr.lon.to_value(u.radian) @@ -68,10 +79,10 @@ def observed_to_icrs(observed_coo, icrs_frame): if isinstance(observed_coo, AltAz): # the 'A' indicates zen/az inputs - coord_type = 'A' + coord_type = "A" lat = PIOVER2 - lat else: - coord_type = 'H' + coord_type = "H" # first set up the astrometry context for ICRS<->CIRS at the observed_coo time astrom = erfa_astrom.get().apco(observed_coo) @@ -81,8 +92,9 @@ def observed_to_icrs(observed_coo, icrs_frame): if is_unitspherical: srepr = SphericalRepresentation(cirs_ra, cirs_dec, 1, copy=False) else: - srepr = SphericalRepresentation(lon=cirs_ra, lat=cirs_dec, - distance=observed_coo.distance, copy=False) + srepr = SphericalRepresentation( + lon=cirs_ra, lat=cirs_dec, distance=observed_coo.distance, copy=False + ) # BCRS (Astrometric) direction to source bcrs_ra, bcrs_dec = aticq(srepr, astrom) << u.radian @@ -91,9 +103,12 @@ def observed_to_icrs(observed_coo, icrs_frame): if is_unitspherical: icrs_srepr = UnitSphericalRepresentation(bcrs_ra, bcrs_dec, copy=False) else: - icrs_srepr = SphericalRepresentation(lon=bcrs_ra, lat=bcrs_dec, - distance=observed_coo.distance, copy=False) - observer_icrs = CartesianRepresentation(astrom['eb'], unit=u.au, xyz_axis=-1, copy=False) + icrs_srepr = SphericalRepresentation( + lon=bcrs_ra, lat=bcrs_dec, distance=observed_coo.distance, copy=False + ) + observer_icrs = CartesianRepresentation( + astrom["eb"], unit=u.au, xyz_axis=-1, copy=False + ) newrepr = icrs_srepr.to_cartesian() + observer_icrs icrs_srepr = newrepr.represent_as(SphericalRepresentation) diff --git a/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py b/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py index ea76b42f5ff..65e6574b745 100644 --- a/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py +++ b/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py @@ -26,7 +26,7 @@ def teme_to_itrs_mat(time): # Sidereal time, rotates from ITRS to mean equinox # Use 1982 model for consistency with Vallado et al (2006) # http://www.celestrak.com/publications/aiaa/2006-6753/AIAA-2006-6753.pdf - gst = erfa.gmst82(*get_jd12(time, 'ut1')) + gst = erfa.gmst82(*get_jd12(time, "ut1")) # Polar Motion # Do not include TIO locator s' because it is not used in Vallado 2006 @@ -43,18 +43,18 @@ def teme_to_itrs_mat(time): def gcrs_to_cirs_mat(time): # celestial-to-intermediate matrix - return erfa.c2i06a(*get_jd12(time, 'tt')) + return erfa.c2i06a(*get_jd12(time, "tt")) def cirs_to_itrs_mat(time): # compute the polar motion p-matrix xp, yp = get_polar_motion(time) - sp = erfa.sp00(*get_jd12(time, 'tt')) + sp = erfa.sp00(*get_jd12(time, "tt")) pmmat = erfa.pom00(xp, yp, sp) # now determine the Earth Rotation Angle for the input obstime # era00 accepts UT1, so we convert if need be - era = erfa.era00(*get_jd12(time, 'ut1')) + era = erfa.era00(*get_jd12(time, "ut1")) # c2tcio expects a GCRS->CIRS matrix, but we just set that to an I-matrix # because we're already in CIRS @@ -68,13 +68,13 @@ def tete_to_itrs_mat(time, rbpn=None): as this is by far the most expensive calculation. """ xp, yp = get_polar_motion(time) - sp = erfa.sp00(*get_jd12(time, 'tt')) + sp = erfa.sp00(*get_jd12(time, "tt")) pmmat = erfa.pom00(xp, yp, sp) # now determine the greenwich apparent sidereal time for the input obstime # we use the 2006A model for consistency with RBPN matrix use in GCRS <-> TETE - ujd1, ujd2 = get_jd12(time, 'ut1') - jd1, jd2 = get_jd12(time, 'tt') + ujd1, ujd2 = get_jd12(time, "ut1") + jd1, jd2 = get_jd12(time, "tt") if rbpn is None: # erfa.gst06a calls pnm06a to calculate rbpn and then gst06. Use it in # favour of getting rbpn with erfa.pnm06a to avoid a possibly large array. @@ -88,7 +88,7 @@ def tete_to_itrs_mat(time, rbpn=None): def gcrs_precession_mat(equinox): - gamb, phib, psib, epsa = erfa.pfw06(*get_jd12(equinox, 'tt')) + gamb, phib, psib, epsa = erfa.pfw06(*get_jd12(equinox, "tt")) return erfa.fw2m(gamb, phib, psib, epsa) @@ -104,22 +104,25 @@ def get_location_gcrs(location, obstime, ref_to_itrs, gcrs_to_ref): it uses the private method that allows passing in the matrices. """ - obsgeoloc, obsgeovel = location._get_gcrs_posvel(obstime, - ref_to_itrs, gcrs_to_ref) + obsgeoloc, obsgeovel = location._get_gcrs_posvel(obstime, ref_to_itrs, gcrs_to_ref) return GCRS(obstime=obstime, obsgeoloc=obsgeoloc, obsgeovel=obsgeovel) # now the actual transforms + @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GCRS, TETE) def gcrs_to_tete(gcrs_coo, tete_frame): # Classical NPB matrix, IAU 2006/2000A # (same as in builtin_frames.utils.get_cip). - rbpn = erfa.pnm06a(*get_jd12(tete_frame.obstime, 'tt')) + rbpn = erfa.pnm06a(*get_jd12(tete_frame.obstime, "tt")) # Get GCRS coordinates for the target observer location and time. - loc_gcrs = get_location_gcrs(tete_frame.location, tete_frame.obstime, - tete_to_itrs_mat(tete_frame.obstime, rbpn=rbpn), - rbpn) + loc_gcrs = get_location_gcrs( + tete_frame.location, + tete_frame.obstime, + tete_to_itrs_mat(tete_frame.obstime, rbpn=rbpn), + rbpn, + ) gcrs_coo2 = gcrs_coo.transform_to(loc_gcrs) # Now we are relative to the correct observer, do the transform to TETE. # These rotations are defined at the geocenter, but can be applied to @@ -132,13 +135,16 @@ def gcrs_to_tete(gcrs_coo, tete_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, TETE, GCRS) def tete_to_gcrs(tete_coo, gcrs_frame): # Compute the pn matrix, and then multiply by its transpose. - rbpn = erfa.pnm06a(*get_jd12(tete_coo.obstime, 'tt')) + rbpn = erfa.pnm06a(*get_jd12(tete_coo.obstime, "tt")) newrepr = tete_coo.cartesian.transform(matrix_transpose(rbpn)) # We now have a GCRS vector for the input location and obstime. # Turn it into a GCRS frame instance. - loc_gcrs = get_location_gcrs(tete_coo.location, tete_coo.obstime, - tete_to_itrs_mat(tete_coo.obstime, rbpn=rbpn), - rbpn) + loc_gcrs = get_location_gcrs( + tete_coo.location, + tete_coo.obstime, + tete_to_itrs_mat(tete_coo.obstime, rbpn=rbpn), + rbpn, + ) gcrs = loc_gcrs.realize_frame(newrepr) # Finally, do any needed offsets (no-op if same obstime and location) return gcrs.transform_to(gcrs_frame) @@ -147,8 +153,9 @@ def tete_to_gcrs(tete_coo, gcrs_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, TETE, ITRS) def tete_to_itrs(tete_coo, itrs_frame): # first get us to TETE at the target obstime, and location (no-op if same) - tete_coo2 = tete_coo.transform_to(TETE(obstime=itrs_frame.obstime, - location=itrs_frame.location)) + tete_coo2 = tete_coo.transform_to( + TETE(obstime=itrs_frame.obstime, location=itrs_frame.location) + ) # now get the pmatrix pmat = tete_to_itrs_mat(itrs_frame.obstime) @@ -172,8 +179,12 @@ def gcrs_to_cirs(gcrs_coo, cirs_frame): # first get the pmatrix pmat = gcrs_to_cirs_mat(cirs_frame.obstime) # Get GCRS coordinates for the target observer location and time. - loc_gcrs = get_location_gcrs(cirs_frame.location, cirs_frame.obstime, - cirs_to_itrs_mat(cirs_frame.obstime), pmat) + loc_gcrs = get_location_gcrs( + cirs_frame.location, + cirs_frame.obstime, + cirs_to_itrs_mat(cirs_frame.obstime), + pmat, + ) gcrs_coo2 = gcrs_coo.transform_to(loc_gcrs) # Now we are relative to the correct observer, do the transform to CIRS. crepr = gcrs_coo2.cartesian.transform(pmat) @@ -187,8 +198,9 @@ def cirs_to_gcrs(cirs_coo, gcrs_frame): newrepr = cirs_coo.cartesian.transform(matrix_transpose(pmat)) # We now have a GCRS vector for the input location and obstime. # Turn it into a GCRS frame instance. - loc_gcrs = get_location_gcrs(cirs_coo.location, cirs_coo.obstime, - cirs_to_itrs_mat(cirs_coo.obstime), pmat) + loc_gcrs = get_location_gcrs( + cirs_coo.location, cirs_coo.obstime, cirs_to_itrs_mat(cirs_coo.obstime), pmat + ) gcrs = loc_gcrs.realize_frame(newrepr) # Finally, do any needed offsets (no-op if same obstime and location) return gcrs.transform_to(gcrs_frame) @@ -197,8 +209,9 @@ def cirs_to_gcrs(cirs_coo, gcrs_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, CIRS, ITRS) def cirs_to_itrs(cirs_coo, itrs_frame): # first get us to CIRS at the target obstime, and location (no-op if same) - cirs_coo2 = cirs_coo.transform_to(CIRS(obstime=itrs_frame.obstime, - location=itrs_frame.location)) + cirs_coo2 = cirs_coo.transform_to( + CIRS(obstime=itrs_frame.obstime, location=itrs_frame.location) + ) # now get the pmatrix pmat = cirs_to_itrs_mat(itrs_frame.obstime) @@ -223,12 +236,18 @@ def itrs_to_cirs(itrs_coo, cirs_frame): # two steps anyway -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, GCRS, PrecessedGeocentric) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, GCRS, PrecessedGeocentric +) def gcrs_to_precessedgeo(from_coo, to_frame): # first get us to GCRS with the right attributes (might be a no-op) - gcrs_coo = from_coo.transform_to(GCRS(obstime=to_frame.obstime, - obsgeoloc=to_frame.obsgeoloc, - obsgeovel=to_frame.obsgeovel)) + gcrs_coo = from_coo.transform_to( + GCRS( + obstime=to_frame.obstime, + obsgeoloc=to_frame.obsgeoloc, + obsgeovel=to_frame.obsgeovel, + ) + ) # now precess to the requested equinox pmat = gcrs_precession_mat(to_frame.equinox) @@ -236,15 +255,19 @@ def gcrs_to_precessedgeo(from_coo, to_frame): return to_frame.realize_frame(crepr) -@frame_transform_graph.transform(FunctionTransformWithFiniteDifference, PrecessedGeocentric, GCRS) +@frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, PrecessedGeocentric, GCRS +) def precessedgeo_to_gcrs(from_coo, to_frame): # first un-precess pmat = gcrs_precession_mat(from_coo.equinox) crepr = from_coo.cartesian.transform(matrix_transpose(pmat)) - gcrs_coo = GCRS(crepr, - obstime=from_coo.obstime, - obsgeoloc=from_coo.obsgeoloc, - obsgeovel=from_coo.obsgeovel) + gcrs_coo = GCRS( + crepr, + obstime=from_coo.obstime, + obsgeoloc=from_coo.obsgeoloc, + obsgeovel=from_coo.obsgeovel, + ) # then move to the GCRS that's actually desired return gcrs_coo.transform_to(to_frame) @@ -274,6 +297,8 @@ def itrs_to_teme(itrs_coo, teme_frame): # Create loopback transformations frame_transform_graph._add_merged_transform(ITRS, CIRS, ITRS) -frame_transform_graph._add_merged_transform(PrecessedGeocentric, GCRS, PrecessedGeocentric) +frame_transform_graph._add_merged_transform( + PrecessedGeocentric, GCRS, PrecessedGeocentric +) frame_transform_graph._add_merged_transform(TEME, ITRS, TEME) frame_transform_graph._add_merged_transform(TETE, ICRS, TETE) diff --git a/astropy/coordinates/builtin_frames/itrs.py b/astropy/coordinates/builtin_frames/itrs.py index 3e8cb6ede60..261728ae58a 100644 --- a/astropy/coordinates/builtin_frames/itrs.py +++ b/astropy/coordinates/builtin_frames/itrs.py @@ -10,7 +10,7 @@ from .utils import DEFAULT_OBSTIME, EARTH_CENTER -__all__ = ['ITRS'] +__all__ = ["ITRS"] doc_footer = """ Other parameters @@ -80,5 +80,6 @@ def earth_location(self): cart = self.represent_as(CartesianRepresentation) return EarthLocation(x=cart.x, y=cart.y, z=cart.z) + # Self-transform is in intermediate_rotation_transforms.py with all the other # ITRS transforms diff --git a/astropy/coordinates/builtin_frames/itrs_observed_transforms.py b/astropy/coordinates/builtin_frames/itrs_observed_transforms.py index de7e3d293df..862b1aea16e 100644 --- a/astropy/coordinates/builtin_frames/itrs_observed_transforms.py +++ b/astropy/coordinates/builtin_frames/itrs_observed_transforms.py @@ -15,7 +15,7 @@ CELMIN = 1e-6 SELMIN = 0.05 # Latitude of the north pole. -NORTH_POLE = 90.0*u.deg +NORTH_POLE = 90.0 * u.deg def itrs_to_altaz_mat(lon, lat): @@ -23,9 +23,7 @@ def itrs_to_altaz_mat(lon, lat): # AltAz frame is left handed minus_x = np.eye(3) minus_x[0][0] = -1.0 - mat = (minus_x - @ rotation_matrix(NORTH_POLE - lat, 'y') - @ rotation_matrix(lon, 'z')) + mat = minus_x @ rotation_matrix(NORTH_POLE - lat, "y") @ rotation_matrix(lon, "z") return mat @@ -34,8 +32,7 @@ def itrs_to_hadec_mat(lon): # HADec frame is left handed minus_y = np.eye(3) minus_y[1][1] = -1.0 - mat = (minus_y - @ rotation_matrix(lon, 'z')) + mat = minus_y @ rotation_matrix(lon, "z") return mat @@ -44,8 +41,7 @@ def altaz_to_hadec_mat(lat): z180 = np.eye(3) z180[0][0] = -1.0 z180[1][1] = -1.0 - mat = (z180 - @ rotation_matrix(NORTH_POLE - lat, 'y')) + mat = z180 @ rotation_matrix(NORTH_POLE - lat, "y") return mat @@ -55,7 +51,7 @@ def add_refraction(aa_crepr, observed_frame): observed_frame.pressure.to_value(u.hPa), observed_frame.temperature.to_value(u.deg_C), observed_frame.relative_humidity.value, - observed_frame.obswl.to_value(u.micron) + observed_frame.obswl.to_value(u.micron), ) # reference: erfa.atioq() norm, uv = erfa.pn(aa_crepr.get_xyz(xyz_axis=-1).to_value()) @@ -64,10 +60,10 @@ def add_refraction(aa_crepr, observed_frame): cel = np.maximum(np.sqrt(uv[..., 0] ** 2 + uv[..., 1] ** 2), CELMIN) # A*tan(z)+B*tan^3(z) model, with Newton-Raphson correction. tan_z = cel / sel - w = refb * tan_z ** 2 - delta_el = (refa + w) * tan_z / (1.0 + (refa + 3.0 * w) / (sel ** 2)) + w = refb * tan_z**2 + delta_el = (refa + w) * tan_z / (1.0 + (refa + 3.0 * w) / (sel**2)) # Apply the change, giving observed vector - cosdel = 1.0 - 0.5 * delta_el ** 2 + cosdel = 1.0 - 0.5 * delta_el**2 f = cosdel - delta_el * sel / cel uv[..., 0] *= f uv[..., 1] *= f @@ -84,7 +80,7 @@ def remove_refraction(aa_crepr, observed_frame): observed_frame.pressure.to_value(u.hPa), observed_frame.temperature.to_value(u.deg_C), observed_frame.relative_humidity.value, - observed_frame.obswl.to_value(u.micron) + observed_frame.obswl.to_value(u.micron), ) # reference: erfa.atoiq() norm, uv = erfa.pn(aa_crepr.get_xyz(xyz_axis=-1).to_value()) @@ -93,7 +89,7 @@ def remove_refraction(aa_crepr, observed_frame): cel = np.sqrt(uv[..., 0] ** 2 + uv[..., 1] ** 2) # A*tan(z)+B*tan^3(z) model tan_z = cel / sel - delta_el = (refa + refb * tan_z ** 2) * tan_z + delta_el = (refa + refb * tan_z**2) * tan_z # Apply the change, giving observed vector. az, el = erfa.c2s(uv) el -= delta_el @@ -105,13 +101,15 @@ def remove_refraction(aa_crepr, observed_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, ITRS, AltAz) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, ITRS, HADec) def itrs_to_observed(itrs_coo, observed_frame): - if (np.any(itrs_coo.location != observed_frame.location) or - np.any(itrs_coo.obstime != observed_frame.obstime)): + if np.any(itrs_coo.location != observed_frame.location) or np.any( + itrs_coo.obstime != observed_frame.obstime + ): # This transform will go through the CIRS and alter stellar aberration. - itrs_coo = itrs_coo.transform_to(ITRS(obstime=observed_frame.obstime, - location=observed_frame.location)) + itrs_coo = itrs_coo.transform_to( + ITRS(obstime=observed_frame.obstime, location=observed_frame.location) + ) - lon, lat, height = observed_frame.location.to_geodetic('WGS84') + lon, lat, height = observed_frame.location.to_geodetic("WGS84") if isinstance(observed_frame, AltAz) or (observed_frame.pressure > 0.0): crepr = itrs_coo.cartesian.transform(itrs_to_altaz_mat(lon, lat)) @@ -127,8 +125,7 @@ def itrs_to_observed(itrs_coo, observed_frame): @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, AltAz, ITRS) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HADec, ITRS) def observed_to_itrs(observed_coo, itrs_frame): - - lon, lat, height = observed_coo.location.to_geodetic('WGS84') + lon, lat, height = observed_coo.location.to_geodetic("WGS84") if isinstance(observed_coo, AltAz) or (observed_coo.pressure > 0.0): crepr = observed_coo.cartesian @@ -138,10 +135,13 @@ def observed_to_itrs(observed_coo, itrs_frame): crepr = remove_refraction(crepr, observed_coo) crepr = crepr.transform(matrix_transpose(itrs_to_altaz_mat(lon, lat))) else: - crepr = observed_coo.cartesian.transform(matrix_transpose(itrs_to_hadec_mat(lon))) + crepr = observed_coo.cartesian.transform( + matrix_transpose(itrs_to_hadec_mat(lon)) + ) - itrs_at_obs_time = ITRS(crepr, obstime=observed_coo.obstime, - location=observed_coo.location) + itrs_at_obs_time = ITRS( + crepr, obstime=observed_coo.obstime, location=observed_coo.location + ) # This final transform may be a no-op if the obstimes and locations are the same. # Otherwise, this transform will go through the CIRS and alter stellar aberration. return itrs_at_obs_time.transform_to(itrs_frame) diff --git a/astropy/coordinates/builtin_frames/lsr.py b/astropy/coordinates/builtin_frames/lsr.py index ee562e462eb..873a5790130 100644 --- a/astropy/coordinates/builtin_frames/lsr.py +++ b/astropy/coordinates/builtin_frames/lsr.py @@ -19,11 +19,11 @@ from .icrs import ICRS # For speed -J2000 = Time('J2000') +J2000 = Time("J2000") -v_bary_Schoenrich2010 = r.CartesianDifferential([11.1, 12.24, 7.25]*u.km/u.s) +v_bary_Schoenrich2010 = r.CartesianDifferential([11.1, 12.24, 7.25] * u.km / u.s) -__all__ = ['LSR', 'GalacticLSR', 'LSRK', 'LSRD'] +__all__ = ["LSR", "GalacticLSR", "LSRK", "LSRD"] doc_footer_lsr = """ @@ -61,8 +61,9 @@ class LSR(BaseRADecFrame): """ # frame attributes: - v_bary = DifferentialAttribute(default=v_bary_Schoenrich2010, - allowed_classes=[r.CartesianDifferential]) + v_bary = DifferentialAttribute( + default=v_bary_Schoenrich2010, allowed_classes=[r.CartesianDifferential] + ) @frame_transform_graph.transform(AffineTransform, ICRS, LSR) @@ -70,7 +71,7 @@ def icrs_to_lsr(icrs_coord, lsr_frame): v_bary_gal = Galactic(lsr_frame.v_bary.to_cartesian()) v_bary_icrs = v_bary_gal.transform_to(icrs_coord) v_offset = v_bary_icrs.data.represent_as(r.CartesianDifferential) - offset = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=v_offset) + offset = r.CartesianRepresentation([0, 0, 0] * u.au, differentials=v_offset) return None, offset @@ -79,7 +80,7 @@ def lsr_to_icrs(lsr_coord, icrs_frame): v_bary_gal = Galactic(lsr_coord.v_bary.to_cartesian()) v_bary_icrs = v_bary_gal.transform_to(icrs_frame) v_offset = v_bary_icrs.data.represent_as(r.CartesianDifferential) - offset = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=-v_offset) + offset = r.CartesianRepresentation([0, 0, 0] * u.au, differentials=-v_offset) return None, offset @@ -136,8 +137,8 @@ class GalacticLSR(BaseCoordinateFrame): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'l'), - RepresentationMapping('lat', 'b') + RepresentationMapping("lon", "l"), + RepresentationMapping("lat", "b"), ] } @@ -152,7 +153,7 @@ class GalacticLSR(BaseCoordinateFrame): def galactic_to_galacticlsr(galactic_coord, lsr_frame): v_bary_gal = Galactic(lsr_frame.v_bary.to_cartesian()) v_offset = v_bary_gal.data.represent_as(r.CartesianDifferential) - offset = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=v_offset) + offset = r.CartesianRepresentation([0, 0, 0] * u.au, differentials=v_offset) return None, offset @@ -160,7 +161,7 @@ def galactic_to_galacticlsr(galactic_coord, lsr_frame): def galacticlsr_to_galactic(lsr_coord, galactic_frame): v_bary_gal = Galactic(lsr_coord.v_bary.to_cartesian()) v_offset = v_bary_gal.data.represent_as(r.CartesianDifferential) - offset = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=-v_offset) + offset = r.CartesianRepresentation([0, 0, 0] * u.au, differentials=-v_offset) return None, offset @@ -197,12 +198,16 @@ class LSRK(BaseRADecFrame): # V_OFFSET_LSRK = ((GORDON1975_V_BARY * GORDON1975_DIRECTION.transform_to(ICRS()).data) # .represent_as(r.CartesianDifferential)) -V_OFFSET_LSRK = r.CartesianDifferential([0.28999706839034606, - -17.317264789717928, - 10.00141199546947]*u.km/u.s) +V_OFFSET_LSRK = r.CartesianDifferential( + [0.28999706839034606, -17.317264789717928, 10.00141199546947] * u.km / u.s +) -ICRS_LSRK_OFFSET = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=V_OFFSET_LSRK) -LSRK_ICRS_OFFSET = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=-V_OFFSET_LSRK) +ICRS_LSRK_OFFSET = r.CartesianRepresentation( + [0, 0, 0] * u.au, differentials=V_OFFSET_LSRK +) +LSRK_ICRS_OFFSET = r.CartesianRepresentation( + [0, 0, 0] * u.au, differentials=-V_OFFSET_LSRK +) @frame_transform_graph.transform(AffineTransform, ICRS, LSRK) @@ -248,12 +253,16 @@ class LSRD(BaseRADecFrame): # V_OFFSET_LSRD = (Galactic(V_BARY_DELHAYE1965.to_cartesian()).transform_to(ICRS()).data # .represent_as(r.CartesianDifferential)) -V_OFFSET_LSRD = r.CartesianDifferential([-0.6382306360182073, - -14.585424483191094, - 7.8011572411006815]*u.km/u.s) +V_OFFSET_LSRD = r.CartesianDifferential( + [-0.6382306360182073, -14.585424483191094, 7.8011572411006815] * u.km / u.s +) -ICRS_LSRD_OFFSET = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=V_OFFSET_LSRD) -LSRD_ICRS_OFFSET = r.CartesianRepresentation([0, 0, 0]*u.au, differentials=-V_OFFSET_LSRD) +ICRS_LSRD_OFFSET = r.CartesianRepresentation( + [0, 0, 0] * u.au, differentials=V_OFFSET_LSRD +) +LSRD_ICRS_OFFSET = r.CartesianRepresentation( + [0, 0, 0] * u.au, differentials=-V_OFFSET_LSRD +) @frame_transform_graph.transform(AffineTransform, ICRS, LSRD) diff --git a/astropy/coordinates/builtin_frames/skyoffset.py b/astropy/coordinates/builtin_frames/skyoffset.py index 3492d52e887..0858d837065 100644 --- a/astropy/coordinates/builtin_frames/skyoffset.py +++ b/astropy/coordinates/builtin_frames/skyoffset.py @@ -43,28 +43,35 @@ def make_skyoffset_cls(framecls): return _skyoffset_cache[framecls] # Create a new SkyOffsetFrame subclass for this frame class. - name = 'SkyOffset' + framecls.__name__ + name = "SkyOffset" + framecls.__name__ _SkyOffsetFramecls = type( - name, (SkyOffsetFrame, framecls), - {'origin': CoordinateAttribute(frame=framecls, default=None), - # The following two have to be done because otherwise we use the - # defaults of SkyOffsetFrame set by BaseCoordinateFrame. - '_default_representation': framecls._default_representation, - '_default_differential': framecls._default_differential, - '__doc__': SkyOffsetFrame.__doc__, - }) - - @frame_transform_graph.transform(FunctionTransform, _SkyOffsetFramecls, _SkyOffsetFramecls) + name, + (SkyOffsetFrame, framecls), + { + "origin": CoordinateAttribute(frame=framecls, default=None), + # The following two have to be done because otherwise we use the + # defaults of SkyOffsetFrame set by BaseCoordinateFrame. + "_default_representation": framecls._default_representation, + "_default_differential": framecls._default_differential, + "__doc__": SkyOffsetFrame.__doc__, + }, + ) + + @frame_transform_graph.transform( + FunctionTransform, _SkyOffsetFramecls, _SkyOffsetFramecls + ) def skyoffset_to_skyoffset(from_skyoffset_coord, to_skyoffset_frame): """Transform between two skyoffset frames.""" # This transform goes through the parent frames on each side. # from_frame -> from_frame.origin -> to_frame.origin -> to_frame - intermediate_from = from_skyoffset_coord.transform_to(from_skyoffset_coord.origin) - intermediate_to = intermediate_from.transform_to(to_skyoffset_frame.origin) - return intermediate_to.transform_to(to_skyoffset_frame) + tmp_from = from_skyoffset_coord.transform_to(from_skyoffset_coord.origin) + tmp_to = tmp_from.transform_to(to_skyoffset_frame.origin) + return tmp_to.transform_to(to_skyoffset_frame) - @frame_transform_graph.transform(DynamicMatrixTransform, framecls, _SkyOffsetFramecls) + @frame_transform_graph.transform( + DynamicMatrixTransform, framecls, _SkyOffsetFramecls + ) def reference_to_skyoffset(reference_frame, skyoffset_frame): """Convert a reference coordinate to an sky offset frame.""" @@ -72,12 +79,14 @@ def reference_to_skyoffset(reference_frame, skyoffset_frame): # relative to the origin. origin = skyoffset_frame.origin.spherical return ( - rotation_matrix(-skyoffset_frame.rotation, 'x') - @ rotation_matrix(-origin.lat, 'y') - @ rotation_matrix(origin.lon, 'z') + rotation_matrix(-skyoffset_frame.rotation, "x") + @ rotation_matrix(-origin.lat, "y") + @ rotation_matrix(origin.lon, "z") ) - @frame_transform_graph.transform(DynamicMatrixTransform, _SkyOffsetFramecls, framecls) + @frame_transform_graph.transform( + DynamicMatrixTransform, _SkyOffsetFramecls, framecls + ) def skyoffset_to_reference(skyoffset_coord, reference_frame): """Convert an sky offset frame coordinate to the reference frame""" @@ -139,10 +148,12 @@ def __new__(cls, *args, **kwargs): if not (issubclass(cls, SkyOffsetFrame) and cls is not SkyOffsetFrame): # We get the origin argument, and handle it here. try: - origin_frame = kwargs['origin'] + origin_frame = kwargs["origin"] except KeyError: - raise TypeError("Can't initialize an SkyOffsetFrame without origin= keyword.") - if hasattr(origin_frame, 'frame'): + raise TypeError( + "Can't initialize a SkyOffsetFrame without origin= keyword." + ) + if hasattr(origin_frame, "frame"): origin_frame = origin_frame.frame newcls = make_skyoffset_cls(origin_frame.__class__) return newcls.__new__(newcls, *args, **kwargs) @@ -158,18 +169,17 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.origin is not None and not self.origin.has_data: - raise ValueError('The origin supplied to SkyOffsetFrame has no ' - 'data.') + raise ValueError("The origin supplied to SkyOffsetFrame has no data.") if self.has_data: self._set_skyoffset_data_lon_wrap_angle(self.data) @staticmethod def _set_skyoffset_data_lon_wrap_angle(data): - if hasattr(data, 'lon'): - data.lon.wrap_angle = 180. * u.deg + if hasattr(data, "lon"): + data.lon.wrap_angle = 180.0 * u.deg return data - def represent_as(self, base, s='base', in_frame_units=False): + def represent_as(self, base, s="base", in_frame_units=False): """ Ensure the wrap angle for any spherical representations. diff --git a/astropy/coordinates/builtin_frames/supergalactic.py b/astropy/coordinates/builtin_frames/supergalactic.py index 98d5c1512fd..0d9f775080e 100644 --- a/astropy/coordinates/builtin_frames/supergalactic.py +++ b/astropy/coordinates/builtin_frames/supergalactic.py @@ -11,7 +11,7 @@ from .galactic import Galactic -__all__ = ['Supergalactic'] +__all__ = ["Supergalactic"] doc_components = """ @@ -45,18 +45,18 @@ class Supergalactic(BaseCoordinateFrame): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'sgl'), - RepresentationMapping('lat', 'sgb') + RepresentationMapping("lon", "sgl"), + RepresentationMapping("lat", "sgb"), ], r.CartesianRepresentation: [ - RepresentationMapping('x', 'sgx'), - RepresentationMapping('y', 'sgy'), - RepresentationMapping('z', 'sgz') + RepresentationMapping("x", "sgx"), + RepresentationMapping("y", "sgy"), + RepresentationMapping("z", "sgz"), ], r.CartesianDifferential: [ - RepresentationMapping('d_x', 'v_x', u.km/u.s), - RepresentationMapping('d_y', 'v_y', u.km/u.s), - RepresentationMapping('d_z', 'v_z', u.km/u.s) + RepresentationMapping("d_x", "v_x", u.km / u.s), + RepresentationMapping("d_y", "v_y", u.km / u.s), + RepresentationMapping("d_z", "v_z", u.km / u.s), ], } @@ -65,4 +65,4 @@ class Supergalactic(BaseCoordinateFrame): # North supergalactic pole in Galactic coordinates. # Needed for transformations to/from Galactic coordinates. - _nsgp_gal = Galactic(l=47.37*u.degree, b=+6.32*u.degree) + _nsgp_gal = Galactic(l=47.37 * u.degree, b=+6.32 * u.degree) diff --git a/astropy/coordinates/builtin_frames/supergalactic_transforms.py b/astropy/coordinates/builtin_frames/supergalactic_transforms.py index 1d51401a432..69a8a7f3937 100644 --- a/astropy/coordinates/builtin_frames/supergalactic_transforms.py +++ b/astropy/coordinates/builtin_frames/supergalactic_transforms.py @@ -11,9 +11,9 @@ @frame_transform_graph.transform(StaticMatrixTransform, Galactic, Supergalactic) def gal_to_supergal(): return ( - rotation_matrix(90, 'z') - @ rotation_matrix(90 - Supergalactic._nsgp_gal.b.degree, 'y') - @ rotation_matrix(Supergalactic._nsgp_gal.l.degree, 'z') + rotation_matrix(90, "z") + @ rotation_matrix(90 - Supergalactic._nsgp_gal.b.degree, "y") + @ rotation_matrix(Supergalactic._nsgp_gal.l.degree, "z") ) diff --git a/astropy/coordinates/builtin_frames/utils.py b/astropy/coordinates/builtin_frames/utils.py index 648164c80b0..33cd0f8a2db 100644 --- a/astropy/coordinates/builtin_frames/utils.py +++ b/astropy/coordinates/builtin_frames/utils.py @@ -21,21 +21,21 @@ # convention for J2000 (it is unclear if there is any "right answer" for B1950) # while #8600 makes this the default behavior, we show it here to ensure it's # clear which is used here -EQUINOX_J2000 = Time('J2000', scale='tt') -EQUINOX_B1950 = Time('B1950', scale='tt') +EQUINOX_J2000 = Time("J2000", scale="tt") +EQUINOX_B1950 = Time("B1950", scale="tt") # This is a time object that is the default "obstime" when such an attribute is # necessary. Currently, we use J2000. -DEFAULT_OBSTIME = Time('J2000', scale='tt') +DEFAULT_OBSTIME = Time("J2000", scale="tt") # This is an EarthLocation that is the default "location" when such an attribute is # necessary. It is the centre of the Earth. -EARTH_CENTER = EarthLocation(0*u.km, 0*u.km, 0*u.km) +EARTH_CENTER = EarthLocation(0 * u.km, 0 * u.km, 0 * u.km) -PIOVER2 = np.pi / 2. +PIOVER2 = np.pi / 2.0 # comes from the mean of the 1962-2014 IERS B data -_DEFAULT_PM = (0.035, 0.29)*u.arcsec +_DEFAULT_PM = (0.035, 0.29) * u.arcsec def get_polar_motion(time): @@ -47,24 +47,23 @@ def get_polar_motion(time): xp, yp, status = iers_table.pm_xy(time, return_status=True) wmsg = ( - 'Tried to get polar motions for times {} IERS data is ' - 'valid. Defaulting to polar motion from the 50-yr mean for those. ' - 'This may affect precision at the arcsec level. Please check your ' - 'astropy.utils.iers.conf.iers_auto_url and point it to a newer ' - 'version if necessary.' + "Tried to get polar motions for times {} IERS data is " + "valid. Defaulting to polar motion from the 50-yr mean for those. " + "This may affect precision at the arcsec level. Please check your " + "astropy.utils.iers.conf.iers_auto_url and point it to a newer " + "version if necessary." ) if np.any(status == iers.TIME_BEFORE_IERS_RANGE): xp[status == iers.TIME_BEFORE_IERS_RANGE] = _DEFAULT_PM[0] yp[status == iers.TIME_BEFORE_IERS_RANGE] = _DEFAULT_PM[1] - warnings.warn(wmsg.format('before'), AstropyWarning) + warnings.warn(wmsg.format("before"), AstropyWarning) if np.any(status == iers.TIME_BEYOND_IERS_RANGE): - xp[status == iers.TIME_BEYOND_IERS_RANGE] = _DEFAULT_PM[0] yp[status == iers.TIME_BEYOND_IERS_RANGE] = _DEFAULT_PM[1] - warnings.warn(wmsg.format('after'), AstropyWarning) + warnings.warn(wmsg.format("after"), AstropyWarning) return xp.to_value(u.radian), yp.to_value(u.radian) @@ -77,7 +76,7 @@ def _warn_iers(ierserr): ---------- ierserr : An `~astropy.utils.iers.IERSRangeError` """ - msg = '{0} Assuming UT1-UTC=0 for coordinate transformations.' + msg = "{0} Assuming UT1-UTC=0 for coordinate transformations." warnings.warn(msg.format(ierserr.args[0]), AstropyWarning) @@ -126,7 +125,7 @@ def norm(p): """ Normalise a p-vector. """ - return p / np.sqrt(np.einsum('...i,...i', p, p))[..., np.newaxis] + return p / np.sqrt(np.einsum("...i,...i", p, p))[..., np.newaxis] def pav2pv(p, v): @@ -134,8 +133,8 @@ def pav2pv(p, v): Combine p- and v- vectors into a pv-vector. """ pv = np.empty(np.broadcast(p, v).shape[:-1], erfa.dt_pv) - pv['p'] = p - pv['v'] = v + pv["p"] = p + pv["v"] = v return pv @@ -213,20 +212,20 @@ def aticq(srepr, astrom): pos = erfa.s2c(srepr.lon.radian, srepr.lat.radian) # Bias-precession-nutation, giving GCRS proper direction. - ppr = erfa.trxp(astrom['bpn'], pos) + ppr = erfa.trxp(astrom["bpn"], pos) # Aberration, giving GCRS natural direction d = np.zeros_like(ppr) for j in range(2): - before = norm(ppr-d) - after = erfa.ab(before, astrom['v'], astrom['em'], astrom['bm1']) + before = norm(ppr - d) + after = erfa.ab(before, astrom["v"], astrom["em"], astrom["bm1"]) d = after - before - pnat = norm(ppr-d) + pnat = norm(ppr - d) # Light deflection by the Sun, giving BCRS coordinate direction d = np.zeros_like(pnat) for j in range(5): - before = norm(pnat-d) + before = norm(pnat - d) if ignore_distance: # No distance to object, assume a long way away q = before @@ -234,7 +233,7 @@ def aticq(srepr, astrom): # Find BCRS direction of Sun to object. # astrom['eh'] and astrom['em'] contain Sun to observer unit vector, # and distance, respectively. - eh = astrom['em'][..., np.newaxis] * astrom['eh'] + eh = astrom["em"][..., np.newaxis] * astrom["eh"] # unit vector from Sun to object q = eh + srepr_distance[..., np.newaxis].to_value(u.au) * before sundist, q = erfa.pn(q) @@ -244,9 +243,9 @@ def aticq(srepr, astrom): # since this is reversible and drops to zero within stellar limb q = np.where(sundist > 1.0e-10, q, before) - after = erfa.ld(1.0, before, q, astrom['eh'], astrom['em'], 1e-6) + after = erfa.ld(1.0, before, q, astrom["eh"], astrom["em"], 1e-6) d = after - before - pco = norm(pnat-d) + pco = norm(pnat - d) # ICRS astrometric RA, Dec rc, dc = erfa.c2s(pco) @@ -305,7 +304,7 @@ def atciqz(srepr, astrom): # Find BCRS direction of Sun to object. # astrom['eh'] and astrom['em'] contain Sun to observer unit vector, # and distance, respectively. - eh = astrom['em'][..., np.newaxis] * astrom['eh'] + eh = astrom["em"][..., np.newaxis] * astrom["eh"] # unit vector from Sun to object q = eh + srepr_distance[..., np.newaxis].to_value(u.au) * pco sundist, q = erfa.pn(q) @@ -316,14 +315,14 @@ def atciqz(srepr, astrom): q = np.where(sundist > 1.0e-10, q, pco) # Light deflection by the Sun, giving BCRS natural direction. - pnat = erfa.ld(1.0, pco, q, astrom['eh'], astrom['em'], 1e-6) + pnat = erfa.ld(1.0, pco, q, astrom["eh"], astrom["em"], 1e-6) # Aberration, giving GCRS proper direction. - ppr = erfa.ab(pnat, astrom['v'], astrom['em'], astrom['bm1']) + ppr = erfa.ab(pnat, astrom["v"], astrom["em"], astrom["bm1"]) # Bias-precession-nutation, giving CIRS proper direction. # Has no effect if matrix is identity matrix, in which case gives GCRS ppr. - pi = erfa.rxp(astrom['bpn'], ppr) + pi = erfa.rxp(astrom["bpn"], ppr) # CIRS (GCRS) RA, Dec ri, di = erfa.c2s(pi) @@ -362,24 +361,24 @@ def prepare_earth_position_vel(time): # This avoids calling epv00 twice, once # in get_body_barycentric_posvel('earth') and once in # get_body_barycentric('sun') - if ephemeris == 'builtin': - jd1, jd2 = get_jd12(time, 'tdb') + if ephemeris == "builtin": + jd1, jd2 = get_jd12(time, "tdb") earth_pv_heliocentric, earth_pv = erfa.epv00(jd1, jd2) - earth_heliocentric = earth_pv_heliocentric['p'] + earth_heliocentric = earth_pv_heliocentric["p"] # all other ephemeris providers probably don't have a shortcut like this else: - earth_p, earth_v = get_body_barycentric_posvel('earth', time) + earth_p, earth_v = get_body_barycentric_posvel("earth", time) # get heliocentric position of earth, preparing it for passing to erfa. - sun = get_body_barycentric('sun', time) + sun = get_body_barycentric("sun", time) earth_heliocentric = (earth_p - sun).get_xyz(xyz_axis=-1).to_value(u.au) # Also prepare earth_pv for passing to erfa, which wants it as # a structured dtype. earth_pv = pav2pv( earth_p.get_xyz(xyz_axis=-1).to_value(u.au), - earth_v.get_xyz(xyz_axis=-1).to_value(u.au / u.d) + earth_v.get_xyz(xyz_axis=-1).to_value(u.au / u.d), ) return earth_pv, earth_heliocentric @@ -406,7 +405,8 @@ def get_offset_sun_from_barycenter(time, include_velocity=False, reverse=False): if include_velocity: # Import here to avoid a circular import from astropy.coordinates.solar_system import get_body_barycentric_posvel - offset_pos, offset_vel = get_body_barycentric_posvel('sun', time) + + offset_pos, offset_vel = get_body_barycentric_posvel("sun", time) if reverse: offset_pos, offset_vel = -offset_pos, -offset_vel offset_vel = offset_vel.represent_as(CartesianDifferential) @@ -415,7 +415,8 @@ def get_offset_sun_from_barycenter(time, include_velocity=False, reverse=False): else: # Import here to avoid a circular import from astropy.coordinates.solar_system import get_body_barycentric - offset_pos = get_body_barycentric('sun', time) + + offset_pos = get_body_barycentric("sun", time) if reverse: offset_pos = -offset_pos diff --git a/astropy/coordinates/calculation.py b/astropy/coordinates/calculation.py index beef39e7fe6..11aa17e3f6b 100644 --- a/astropy/coordinates/calculation.py +++ b/astropy/coordinates/calculation.py @@ -26,58 +26,91 @@ class CelestialError(ValueError): def get_sign(dt): - """ - """ - if ((int(dt.month) == 12 and int(dt.day) >= 22) or - (int(dt.month) == 1 and int(dt.day) <= 19)): + """ """ + if (int(dt.month) == 12 and int(dt.day) >= 22) or ( + int(dt.month) == 1 and int(dt.day) <= 19 + ): zodiac_sign = "capricorn" - elif ((int(dt.month) == 1 and int(dt.day) >= 20) or - (int(dt.month) == 2 and int(dt.day) <= 17)): + elif (int(dt.month) == 1 and int(dt.day) >= 20) or ( + int(dt.month) == 2 and int(dt.day) <= 17 + ): zodiac_sign = "aquarius" - elif ((int(dt.month) == 2 and int(dt.day) >= 18) or - (int(dt.month) == 3 and int(dt.day) <= 19)): + elif (int(dt.month) == 2 and int(dt.day) >= 18) or ( + int(dt.month) == 3 and int(dt.day) <= 19 + ): zodiac_sign = "pisces" - elif ((int(dt.month) == 3 and int(dt.day) >= 20) or - (int(dt.month) == 4 and int(dt.day) <= 19)): + elif (int(dt.month) == 3 and int(dt.day) >= 20) or ( + int(dt.month) == 4 and int(dt.day) <= 19 + ): zodiac_sign = "aries" - elif ((int(dt.month) == 4 and int(dt.day) >= 20) or - (int(dt.month) == 5 and int(dt.day) <= 20)): + elif (int(dt.month) == 4 and int(dt.day) >= 20) or ( + int(dt.month) == 5 and int(dt.day) <= 20 + ): zodiac_sign = "taurus" - elif ((int(dt.month) == 5 and int(dt.day) >= 21) or - (int(dt.month) == 6 and int(dt.day) <= 20)): + elif (int(dt.month) == 5 and int(dt.day) >= 21) or ( + int(dt.month) == 6 and int(dt.day) <= 20 + ): zodiac_sign = "gemini" - elif ((int(dt.month) == 6 and int(dt.day) >= 21) or - (int(dt.month) == 7 and int(dt.day) <= 22)): + elif (int(dt.month) == 6 and int(dt.day) >= 21) or ( + int(dt.month) == 7 and int(dt.day) <= 22 + ): zodiac_sign = "cancer" - elif ((int(dt.month) == 7 and int(dt.day) >= 23) or - (int(dt.month) == 8 and int(dt.day) <= 22)): + elif (int(dt.month) == 7 and int(dt.day) >= 23) or ( + int(dt.month) == 8 and int(dt.day) <= 22 + ): zodiac_sign = "leo" - elif ((int(dt.month) == 8 and int(dt.day) >= 23) or - (int(dt.month) == 9 and int(dt.day) <= 22)): + elif (int(dt.month) == 8 and int(dt.day) >= 23) or ( + int(dt.month) == 9 and int(dt.day) <= 22 + ): zodiac_sign = "virgo" - elif ((int(dt.month) == 9 and int(dt.day) >= 23) or - (int(dt.month) == 10 and int(dt.day) <= 22)): + elif (int(dt.month) == 9 and int(dt.day) >= 23) or ( + int(dt.month) == 10 and int(dt.day) <= 22 + ): zodiac_sign = "libra" - elif ((int(dt.month) == 10 and int(dt.day) >= 23) or - (int(dt.month) == 11 and int(dt.day) <= 21)): + elif (int(dt.month) == 10 and int(dt.day) >= 23) or ( + int(dt.month) == 11 and int(dt.day) <= 21 + ): zodiac_sign = "scorpio" - elif ((int(dt.month) == 11 and int(dt.day) >= 22) or - (int(dt.month) == 12 and int(dt.day) <= 21)): + elif (int(dt.month) == 11 and int(dt.day) >= 22) or ( + int(dt.month) == 12 and int(dt.day) <= 21 + ): zodiac_sign = "sagittarius" return zodiac_sign -_VALID_SIGNS = ["capricorn", "aquarius", "pisces", "aries", "taurus", "gemini", - "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius"] +_VALID_SIGNS = [ + "capricorn", + "aquarius", + "pisces", + "aries", + "taurus", + "gemini", + "cancer", + "leo", + "virgo", + "libra", + "scorpio", + "sagittarius", +] # Some of the constellation names map to different astrological "sign names". # Astrologers really needs to talk to the IAU... -_CONST_TO_SIGNS = {'capricornus': 'capricorn', 'scorpius': 'scorpio'} - -_ZODIAC = ((1900, "rat"), (1901, "ox"), (1902, "tiger"), - (1903, "rabbit"), (1904, "dragon"), (1905, "snake"), - (1906, "horse"), (1907, "goat"), (1908, "monkey"), - (1909, "rooster"), (1910, "dog"), (1911, "pig")) +_CONST_TO_SIGNS = {"capricornus": "capricorn", "scorpius": "scorpio"} + +_ZODIAC = ( + (1900, "rat"), + (1901, "ox"), + (1902, "tiger"), + (1903, "rabbit"), + (1904, "dragon"), + (1905, "snake"), + (1906, "horse"), + (1907, "goat"), + (1908, "monkey"), + (1909, "rooster"), + (1910, "dog"), + (1911, "pig"), +) # https://stackoverflow.com/questions/12791871/chinese-zodiac-python-program @@ -115,34 +148,36 @@ def horoscope(birthday, corrected=True, chinese=False): today = datetime.now() err_msg = "Invalid response from celestial gods (failed to load horoscope)." - headers = {'User-Agent': 'foo/bar'} + headers = {"User-Agent": "foo/bar"} special_words = { - '([sS]tar[s^ ]*)': 'yellow', - '([yY]ou[^ ]*)': 'magenta', - '([pP]lay[^ ]*)': 'blue', - '([hH]eart)': 'red', - '([fF]ate)': 'lightgreen', + "([sS]tar[s^ ]*)": "yellow", + "([yY]ou[^ ]*)": "magenta", + "([pP]lay[^ ]*)": "blue", + "([hH]eart)": "red", + "([fF]ate)": "lightgreen", } if isinstance(birthday, str): - birthday = datetime.strptime(birthday, '%Y-%m-%d') + birthday = datetime.strptime(birthday, "%Y-%m-%d") if chinese: # TODO: Make this more accurate by using the actual date, not just year # Might need third-party tool like https://pypi.org/project/lunardate zodiac_sign = _get_zodiac(birthday.year) - url = ('https://www.horoscope.com/us/horoscopes/yearly/' - '{}-chinese-horoscope-{}.aspx'.format(today.year, zodiac_sign)) - summ_title_sfx = f'in {today.year}' + url = ( + "https://www.horoscope.com/us/horoscopes/yearly/" + f"{today.year}-chinese-horoscope-{zodiac_sign}.aspx" + ) + summ_title_sfx = f"in {today.year}" try: res = Request(url, headers=headers) with urlopen(res) as f: try: - doc = BeautifulSoup(f, 'html.parser') + doc = BeautifulSoup(f, "html.parser") # TODO: Also include Love, Family & Friends, Work, Money, More? - item = doc.find(id='overview') + item = doc.find(id="overview") desc = item.getText() except Exception: raise CelestialError(err_msg) @@ -154,14 +189,15 @@ def horoscope(birthday, corrected=True, chinese=False): if corrected: with warnings.catch_warnings(): - warnings.simplefilter('ignore') # Ignore ErfaWarning + warnings.simplefilter("ignore") # Ignore ErfaWarning zodiac_sign = get_sun(birthday).get_constellation().lower() zodiac_sign = _CONST_TO_SIGNS.get(zodiac_sign, zodiac_sign) if zodiac_sign not in _VALID_SIGNS: - raise HumanError('On your birthday the sun was in {}, which is not ' - 'a sign of the zodiac. You must not exist. Or ' - 'maybe you can settle for ' - 'corrected=False.'.format(zodiac_sign.title())) + raise HumanError( + f"On your birthday the sun was in {zodiac_sign.title()}, which is" + " not a sign of the zodiac. You must not exist. Or maybe you can" + " settle for corrected=False." + ) else: zodiac_sign = get_sign(birthday.to_datetime()) url = f"https://astrology.com/horoscope/daily/{zodiac_sign}.html" @@ -170,16 +206,15 @@ def horoscope(birthday, corrected=True, chinese=False): res = Request(url, headers=headers) with urlopen(res) as f: try: - doc = BeautifulSoup(f, 'html.parser') - item = doc.find('div', {'id': 'content'}) + doc = BeautifulSoup(f, "html.parser") + item = doc.find("div", {"id": "content"}) desc = item.getText() except Exception: raise CelestialError(err_msg) - print("*"*79) - color_print(f"Horoscope for {zodiac_sign.capitalize()} {summ_title_sfx}:", - 'green') - print("*"*79) + print("*" * 79) + color_print(f"Horoscope for {zodiac_sign.capitalize()} {summ_title_sfx}:", "green") + print("*" * 79) for block in textwrap.wrap(desc, 79): split_block = block.split() for i, word in enumerate(split_block): @@ -193,6 +228,7 @@ def horoscope(birthday, corrected=True, chinese=False): def inject_horoscope(): import astropy + astropy._yourfuture = horoscope diff --git a/astropy/coordinates/distances.py b/astropy/coordinates/distances.py index a8b9f825993..b1006df949d 100644 --- a/astropy/coordinates/distances.py +++ b/astropy/coordinates/distances.py @@ -14,10 +14,10 @@ from .angles import Angle -__all__ = ['Distance'] +__all__ = ["Distance"] -__doctest_requires__ = {'*': ['scipy']} +__doctest_requires__ = {"*": ["scipy"]} class Distance(u.SpecificTypeQuantity): @@ -100,17 +100,32 @@ class Distance(u.SpecificTypeQuantity): _equivalent_unit = u.m _include_easy_conversion_members = True - def __new__(cls, value=None, unit=None, z=None, cosmology=None, - distmod=None, parallax=None, dtype=np.inexact, copy=True, - order=None, subok=False, ndmin=0, allow_negative=False): - + def __new__( + cls, + value=None, + unit=None, + z=None, + cosmology=None, + distmod=None, + parallax=None, + dtype=np.inexact, + copy=True, + order=None, + subok=False, + ndmin=0, + allow_negative=False, + ): n_not_none = sum(x is not None for x in [value, z, distmod, parallax]) if n_not_none == 0: - raise ValueError('none of `value`, `z`, `distmod`, or `parallax` ' - 'were given to Distance constructor') + raise ValueError( + "none of `value`, `z`, `distmod`, or `parallax` " + "were given to Distance constructor" + ) elif n_not_none > 1: - raise ValueError('more than one of `value`, `z`, `distmod`, or ' - '`parallax` were given to Distance constructor') + raise ValueError( + "more than one of `value`, `z`, `distmod`, or " + "`parallax` were given to Distance constructor" + ) if value is None: # If something else but `value` was provided then a new array will @@ -120,13 +135,16 @@ def __new__(cls, value=None, unit=None, z=None, cosmology=None, if z is not None: if cosmology is None: from astropy.cosmology import default_cosmology + cosmology = default_cosmology.get() value = cosmology.luminosity_distance(z) elif cosmology is not None: - raise ValueError('a `cosmology` was given but `z` was not ' - 'provided in Distance constructor') + raise ValueError( + "a `cosmology` was given but `z` was not " + "provided in Distance constructor" + ) elif distmod is not None: value = cls._distmod_to_pc(distmod) @@ -151,27 +169,38 @@ def __new__(cls, value=None, unit=None, z=None, cosmology=None, if np.any(parallax < 0): if allow_negative: warnings.warn( - "negative parallaxes are converted to NaN " - "distances even when `allow_negative=True`, " - "because negative parallaxes cannot be transformed " - "into distances. See the discussion in this paper: " - "https://arxiv.org/abs/1507.02105", AstropyWarning) + "negative parallaxes are converted to NaN distances even when" + " `allow_negative=True`, because negative parallaxes cannot be" + " transformed into distances. See the discussion in this paper:" + " https://arxiv.org/abs/1507.02105", + AstropyWarning, + ) else: raise ValueError( "some parallaxes are negative, which are not " "interpretable as distances. See the discussion in " "this paper: https://arxiv.org/abs/1507.02105 . You " "can convert negative parallaxes to NaN distances by " - "providing the `allow_negative=True` argument.") + "providing the `allow_negative=True` argument." + ) # now we have arguments like for a Quantity, so let it do the work distance = super().__new__( - cls, value, unit, dtype=dtype, copy=copy, order=order, - subok=subok, ndmin=ndmin) + cls, + value, + unit, + dtype=dtype, + copy=copy, + order=order, + subok=subok, + ndmin=ndmin, + ) if not allow_negative and np.any(distance.value < 0): - raise ValueError("distance must be >= 0. Use the argument " - "`allow_negative=True` to allow negative values.") + raise ValueError( + "distance must be >= 0. Use the argument " + "`allow_negative=True` to allow negative values." + ) return distance @@ -216,21 +245,22 @@ def compute_z(self, cosmology=None, **atzkw): if cosmology is None: from astropy.cosmology import default_cosmology + cosmology = default_cosmology.get() - atzkw.setdefault("ztol", 1.e-10) + atzkw.setdefault("ztol", 1.0e-10) return z_at_value(cosmology.luminosity_distance, self, **atzkw) @property def distmod(self): """The distance modulus as a `~astropy.units.Quantity`""" - val = 5. * np.log10(self.to_value(u.pc)) - 5. + val = 5.0 * np.log10(self.to_value(u.pc)) - 5.0 return u.Quantity(val, u.mag, copy=False) @classmethod def _distmod_to_pc(cls, dm): dm = u.Quantity(dm, u.mag) - return cls(10 ** ((dm.value + 5) / 5.), u.pc, copy=False) + return cls(10 ** ((dm.value + 5) / 5.0), u.pc, copy=False) @property def parallax(self): diff --git a/astropy/coordinates/earth.py b/astropy/coordinates/earth.py index 05fa2d72f72..d8a04ede103 100644 --- a/astropy/coordinates/earth.py +++ b/astropy/coordinates/earth.py @@ -27,18 +27,23 @@ CartesianRepresentation, ) -__all__ = ['EarthLocation', 'BaseGeodeticRepresentation', - 'WGS84GeodeticRepresentation', 'WGS72GeodeticRepresentation', - 'GRS80GeodeticRepresentation'] +__all__ = [ + "EarthLocation", + "BaseGeodeticRepresentation", + "WGS84GeodeticRepresentation", + "WGS72GeodeticRepresentation", + "GRS80GeodeticRepresentation", +] -GeodeticLocation = collections.namedtuple('GeodeticLocation', ['lon', 'lat', 'height']) +GeodeticLocation = collections.namedtuple("GeodeticLocation", ["lon", "lat", "height"]) ELLIPSOIDS = {} """Available ellipsoids (defined in erfam.h, with numbers exposed in erfa).""" # Note: they get filled by the creation of the geodetic classes. -OMEGA_EARTH = ((1.002_737_811_911_354_48 * u.cycle/u.day) - .to(1/u.s, u.dimensionless_angles())) +OMEGA_EARTH = (1.002_737_811_911_354_48 * u.cycle / u.day).to( + 1 / u.s, u.dimensionless_angles() +) """ Rotational velocity of Earth, following SOFA's pvtob. @@ -51,22 +56,22 @@ """ -def _check_ellipsoid(ellipsoid=None, default='WGS84'): +def _check_ellipsoid(ellipsoid=None, default="WGS84"): if ellipsoid is None: ellipsoid = default if ellipsoid not in ELLIPSOIDS: - raise ValueError(f'Ellipsoid {ellipsoid} not among known ones ({ELLIPSOIDS})') + raise ValueError(f"Ellipsoid {ellipsoid} not among known ones ({ELLIPSOIDS})") return ellipsoid def _get_json_result(url, err_str, use_google): - # need to do this here to prevent a series of complicated circular imports from .name_resolve import NameResolveError + try: # Retrieve JSON response from Google maps API resp = urllib.request.urlopen(url, timeout=data.conf.remote_timeout) - resp_data = json.loads(resp.read().decode('utf8')) + resp_data = json.loads(resp.read().decode("utf8")) except urllib.error.URLError as e: # This catches a timeout error, see: @@ -83,11 +88,12 @@ def _get_json_result(url, err_str, use_google): raise NameResolveError(err_str.format(msg="connection timed out")) if use_google: - results = resp_data.get('results', []) + results = resp_data.get("results", []) - if resp_data.get('status', None) != 'OK': - raise NameResolveError(err_str.format(msg="unknown failure with " - "Google API")) + if resp_data.get("status", None) != "OK": + raise NameResolveError( + err_str.format(msg="unknown failure with Google API") + ) else: # OpenStreetMap returns a list results = resp_data @@ -104,17 +110,18 @@ class EarthLocationInfo(QuantityInfoBase): required when the object is used as a mixin column within a table, but can be used as a general way to store meta information. """ - _represent_as_dict_attrs = ('x', 'y', 'z', 'ellipsoid') + + _represent_as_dict_attrs = ("x", "y", "z", "ellipsoid") def _construct_from_dict(self, map): # Need to pop ellipsoid off and update post-instantiation. This is # on the to-fix list in #4261. - ellipsoid = map.pop('ellipsoid') + ellipsoid = map.pop("ellipsoid") out = self._parent_cls(**map) out.ellipsoid = ellipsoid return out - def new_like(self, cols, length, metadata_conflicts='warn', name=None): + def new_like(self, cols, length, metadata_conflicts="warn", name=None): """ Return a new EarthLocation instance which is consistent with the input ``cols`` and has ``length`` rows. @@ -141,20 +148,24 @@ def new_like(self, cols, length, metadata_conflicts='warn', name=None): # Very similar to QuantityInfo.new_like, but the creation of the # map is different enough that this needs its own rouinte. # Get merged info attributes shape, dtype, format, description. - attrs = self.merge_cols_attributes(cols, metadata_conflicts, name, - ('meta', 'format', 'description')) + attrs = self.merge_cols_attributes( + cols, metadata_conflicts, name, ("meta", "format", "description") + ) # The above raises an error if the dtypes do not match, but returns # just the string representation, which is not useful, so remove. - attrs.pop('dtype') + attrs.pop("dtype") # Make empty EarthLocation using the dtype and unit of the last column. # Use zeros so we do not get problems for possible conversion to # geodetic coordinates. - shape = (length,) + attrs.pop('shape') - data = u.Quantity(np.zeros(shape=shape, dtype=cols[0].dtype), - unit=cols[0].unit, copy=False) + shape = (length,) + attrs.pop("shape") + data = u.Quantity( + np.zeros(shape=shape, dtype=cols[0].dtype), unit=cols[0].unit, copy=False + ) # Get arguments needed to reconstruct class - map = {key: (data[key] if key in 'xyz' else getattr(cols[-1], key)) - for key in self._represent_as_dict_attrs} + map = { + key: (data[key] if key in "xyz" else getattr(cols[-1], key)) + for key in self._represent_as_dict_attrs + } out = self._construct_from_dict(map) # Set remaining info attributes for attr, value in attrs.items(): @@ -188,17 +199,15 @@ class methods (`from_geocentric` and `from_geodetic`) or initialize the property. """ - _ellipsoid = 'WGS84' - _location_dtype = np.dtype({'names': ['x', 'y', 'z'], - 'formats': [np.float64]*3}) + _ellipsoid = "WGS84" + _location_dtype = np.dtype({"names": ["x", "y", "z"], "formats": [np.float64] * 3}) _array_dtype = np.dtype((np.float64, (3,))) info = EarthLocationInfo() def __new__(cls, *args, **kwargs): # TODO: needs copy argument and better dealing with inputs. - if (len(args) == 1 and len(kwargs) == 0 and - isinstance(args[0], EarthLocation)): + if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], EarthLocation): return args[0].copy() try: self = cls.from_geocentric(*args, **kwargs) @@ -206,10 +215,11 @@ def __new__(cls, *args, **kwargs): try: self = cls.from_geodetic(*args, **kwargs) except Exception as exc_geodetic: - raise TypeError('Coordinates could not be parsed as either ' - 'geocentric or geodetic, with respective ' - 'exceptions "{}" and "{}"' - .format(exc_geocentric, exc_geodetic)) + raise TypeError( + "Coordinates could not be parsed as either " + "geocentric or geodetic, with respective " + f'exceptions "{exc_geocentric}" and "{exc_geodetic}"' + ) return self @classmethod @@ -239,30 +249,30 @@ def from_geocentric(cls, x, y, z, unit=None): try: unit = x.unit except AttributeError: - raise TypeError("Geocentric coordinates should be Quantities " - "unless an explicit unit is given.") from None + raise TypeError( + "Geocentric coordinates should be Quantities " + "unless an explicit unit is given." + ) from None else: unit = u.Unit(unit) - if unit.physical_type != 'length': - raise u.UnitsError("Geocentric coordinates should be in " - "units of length.") + if unit.physical_type != "length": + raise u.UnitsError("Geocentric coordinates should be in units of length.") try: x = u.Quantity(x, unit, copy=False) y = u.Quantity(y, unit, copy=False) z = u.Quantity(z, unit, copy=False) except u.UnitsError: - raise u.UnitsError("Geocentric coordinate units should all be " - "consistent.") + raise u.UnitsError("Geocentric coordinate units should all be consistent.") x, y, z = np.broadcast_arrays(x, y, z) struc = np.empty(x.shape, cls._location_dtype) - struc['x'], struc['y'], struc['z'] = x, y, z + struc["x"], struc["y"], struc["z"] = x, y, z return super().__new__(cls, struc, unit, copy=False) @classmethod - def from_geodetic(cls, lon, lat, height=0., ellipsoid=None): + def from_geodetic(cls, lon, lat, height=0.0, ellipsoid=None): """ Location on Earth, initialized from geodetic coordinates. @@ -366,8 +376,9 @@ class = EarthLocation try: el = registry[site_name] except UnknownSiteException as e: - raise UnknownSiteException(e.site, 'EarthLocation.get_site_names', - close_names=e.close_names) from e + raise UnknownSiteException( + e.site, "EarthLocation.get_site_names", close_names=e.close_names + ) from e if cls is el.__class__: return el @@ -432,52 +443,50 @@ def of_address(cls, address, get_height=False, google_api_key=None): # Fail fast if invalid options are passed: if not use_google and get_height: raise ValueError( - 'Currently, `get_height` only works when using ' - 'the Google geocoding API, which requires passing ' - 'a Google API key with `google_api_key`. See: ' - 'https://developers.google.com/maps/documentation/geocoding/get-api-key ' - 'for information on obtaining an API key.') + "Currently, `get_height` only works when using the Google geocoding" + " API, which requires passing a Google API key with `google_api_key`." + " See:" + " https://developers.google.com/maps/documentation/geocoding/get-api-key" + " for information on obtaining an API key." + ) if use_google: # Google - pars = urllib.parse.urlencode({'address': address, - 'key': google_api_key}) + pars = urllib.parse.urlencode({"address": address, "key": google_api_key}) geo_url = f"https://maps.googleapis.com/maps/api/geocode/json?{pars}" else: # OpenStreetMap - pars = urllib.parse.urlencode({'q': address, - 'format': 'json'}) + pars = urllib.parse.urlencode({"q": address, "format": "json"}) geo_url = f"https://nominatim.openstreetmap.org/search?{pars}" # get longitude and latitude location err_str = f"Unable to retrieve coordinates for address '{address}'; {{msg}}" - geo_result = _get_json_result(geo_url, err_str=err_str, - use_google=use_google) + geo_result = _get_json_result(geo_url, err_str=err_str, use_google=use_google) if use_google: - loc = geo_result[0]['geometry']['location'] - lat = loc['lat'] - lon = loc['lng'] + loc = geo_result[0]["geometry"]["location"] + lat = loc["lat"] + lon = loc["lng"] else: loc = geo_result[0] - lat = float(loc['lat']) # strings are returned by OpenStreetMap - lon = float(loc['lon']) + lat = float(loc["lat"]) # strings are returned by OpenStreetMap + lon = float(loc["lon"]) if get_height: - pars = {'locations': f'{lat:.8f},{lon:.8f}', - 'key': google_api_key} + pars = {"locations": f"{lat:.8f},{lon:.8f}", "key": google_api_key} pars = urllib.parse.urlencode(pars) ele_url = f"https://maps.googleapis.com/maps/api/elevation/json?{pars}" err_str = f"Unable to retrieve elevation for address '{address}'; {{msg}}" - ele_result = _get_json_result(ele_url, err_str=err_str, - use_google=use_google) - height = ele_result[0]['elevation']*u.meter + ele_result = _get_json_result( + ele_url, err_str=err_str, use_google=use_google + ) + height = ele_result[0]["elevation"] * u.meter else: - height = 0. + height = 0.0 - return cls.from_geodetic(lon=lon*u.deg, lat=lat*u.deg, height=height) + return cls.from_geodetic(lon=lon * u.deg, lat=lat * u.deg, height=height) @classmethod def get_site_names(cls): @@ -531,12 +540,12 @@ def _get_site_registry(cls, force_download=False, force_builtin=False): from .sites import get_builtin_sites, get_downloaded_sites if force_builtin and force_download: - raise ValueError('Cannot have both force_builtin and force_download True') + raise ValueError("Cannot have both force_builtin and force_download True") if force_builtin: reg = cls._site_registry = get_builtin_sites() else: - reg = getattr(cls, '_site_registry', None) + reg = getattr(cls, "_site_registry", None) if force_download or not reg: try: if isinstance(force_download, str): @@ -546,10 +555,12 @@ def _get_site_registry(cls, force_download=False, force_builtin=False): except OSError: if force_download: raise - msg = ('Could not access the online site list. Falling ' - 'back on the built-in version, which is rather ' - 'limited. If you want to retry the download, do ' - '{0}._get_site_registry(force_download=True)') + msg = ( + "Could not access the online site list. Falling " + "back on the built-in version, which is rather " + "limited. If you want to retry the download, do " + "{0}._get_site_registry(force_download=True)" + ) warn(AstropyUserWarning(msg.format(cls.__name__))) reg = get_builtin_sites() cls._site_registry = reg @@ -599,10 +610,13 @@ def to_geodetic(self, ellipsoid=None): ellipsoid = _check_ellipsoid(ellipsoid, default=self.ellipsoid) xyz = self.view(self._array_dtype, u.Quantity) llh = CartesianRepresentation(xyz, xyz_axis=-1, copy=False).represent_as( - ELLIPSOIDS[ellipsoid]) + ELLIPSOIDS[ellipsoid] + ) return GeodeticLocation( - Longitude(llh.lon, u.deg, wrap_angle=180*u.deg, copy=False), - llh.lat << u.deg, llh.height << self.unit) + Longitude(llh.lon, u.deg, wrap_angle=180 * u.deg, copy=False), + llh.lat << u.deg, + llh.height << self.unit, + ) @property def lon(self): @@ -652,11 +666,15 @@ def get_itrs(self, obstime=None): # do this here to prevent a series of complicated circular imports from .builtin_frames import ITRS + return ITRS(x=self.x, y=self.y, z=self.z, obstime=obstime) - itrs = property(get_itrs, doc="""An `~astropy.coordinates.ITRS` object with - for the location of this object at the - default ``obstime``.""") + itrs = property( + get_itrs, + doc="""An `~astropy.coordinates.ITRS` object + for the location of this object at the + default ``obstime``.""", + ) def get_gcrs(self, obstime): """GCRS position with velocity at ``obstime`` as a GCRS coordinate. @@ -673,8 +691,9 @@ def get_gcrs(self, obstime): """ # do this here to prevent a series of complicated circular imports from .builtin_frames import GCRS + loc, vel = self.get_gcrs_posvel(obstime) - loc.differentials['s'] = CartesianDifferential.from_cartesian(vel) + loc.differentials["s"] = CartesianDifferential.from_cartesian(vel) return GCRS(loc, obstime=obstime) def _get_gcrs_posvel(self, obstime, ref_to_itrs, gcrs_to_ref): @@ -702,8 +721,9 @@ def _get_gcrs_posvel(self, obstime, ref_to_itrs, gcrs_to_ref): itrs_to_gcrs = ref_to_gcrs @ matrix_transpose(ref_to_itrs) # Earth's rotation vector in the ref frame is rot_vec_ref = (0,0,OMEGA_EARTH), # so in GCRS it is rot_vec_gcrs[..., 2] @ OMEGA_EARTH. - rot_vec_gcrs = CartesianRepresentation(ref_to_gcrs[..., 2] * OMEGA_EARTH, - xyz_axis=-1, copy=False) + rot_vec_gcrs = CartesianRepresentation( + ref_to_gcrs[..., 2] * OMEGA_EARTH, xyz_axis=-1, copy=False + ) # Get the position in the GCRS frame. # Since we just need the cartesian representation of ITRS, avoid get_itrs(). itrs_cart = CartesianRepresentation(self.x, self.y, self.z, copy=False) @@ -735,13 +755,13 @@ def get_gcrs_posvel(self, obstime): ) # Get gcrs_posvel by transforming via CIRS (slightly faster than TETE). - return self._get_gcrs_posvel(obstime, - cirs_to_itrs_mat(obstime), - gcrs_to_cirs_mat(obstime)) + return self._get_gcrs_posvel( + obstime, cirs_to_itrs_mat(obstime), gcrs_to_cirs_mat(obstime) + ) - def gravitational_redshift(self, obstime, - bodies=['sun', 'jupiter', 'moon'], - masses={}): + def gravitational_redshift( + self, obstime, bodies=["sun", "jupiter", "moon"], masses={} + ): """Return the gravitational redshift at this EarthLocation. Calculates the gravitational redshift, of order 3 m/s, due to the @@ -775,24 +795,30 @@ def gravitational_redshift(self, obstime, bodies = list(bodies) # Ensure earth is included and last in the list. - if 'earth' in bodies: - bodies.remove('earth') - bodies.append('earth') - _masses = {'sun': consts.GM_sun, - 'jupiter': consts.GM_jup, - 'moon': consts.G * 7.34767309e22*u.kg, - 'earth': consts.GM_earth} + if "earth" in bodies: + bodies.remove("earth") + bodies.append("earth") + _masses = { + "sun": consts.GM_sun, + "jupiter": consts.GM_jup, + "moon": consts.G * 7.34767309e22 * u.kg, + "earth": consts.GM_earth, + } _masses.update(masses) GMs = [] M_GM_equivalency = (u.kg, u.Unit(consts.G * u.kg)) for body in bodies: try: - GMs.append(_masses[body].to(u.m**3/u.s**2, [M_GM_equivalency])) + GMs.append(_masses[body].to(u.m**3 / u.s**2, [M_GM_equivalency])) except KeyError as err: raise KeyError(f'body "{body}" does not have a mass.') from err except u.UnitsError as exc: - exc.args += ('"masses" argument values must be masses or ' - 'gravitational parameters.',) + exc.args += ( + ( + '"masses" argument values must be masses or ' + "gravitational parameters." + ), + ) raise positions = [get_body_barycentric(name, obstime) for name in bodies] @@ -801,8 +827,9 @@ def gravitational_redshift(self, obstime, # Append distance from Earth's center for Earth's contribution. distances.append(CartesianRepresentation(self.geocentric).norm()) # Get redshifts due to all objects. - redshifts = [-GM / consts.c / distance for (GM, distance) in - zip(GMs, distances)] + redshifts = [ + -GM / consts.c / distance for (GM, distance) in zip(GMs, distances) + ] # Reverse order of summing, to go from small to big, and to get # "earth" first, which gives m/s as unit. return sum(redshifts[::-1]) @@ -810,17 +837,17 @@ def gravitational_redshift(self, obstime, @property def x(self): """The X component of the geocentric coordinates.""" - return self['x'] + return self["x"] @property def y(self): """The Y component of the geocentric coordinates.""" - return self['y'] + return self["y"] @property def z(self): """The Z component of the geocentric coordinates.""" - return self['z'] + return self["z"] def __getitem__(self, item): result = super().__getitem__(item) @@ -831,12 +858,12 @@ def __getitem__(self, item): def __array_finalize__(self, obj): super().__array_finalize__(obj) - if hasattr(obj, '_ellipsoid'): + if hasattr(obj, "_ellipsoid"): self._ellipsoid = obj._ellipsoid def __len__(self): if self.shape == (): - raise IndexError('0-d EarthLocation arrays cannot be indexed') + raise IndexError("0-d EarthLocation arrays cannot be indexed") else: return super().__len__() @@ -876,13 +903,11 @@ def _to_value(self, unit, equivalencies=[]): class BaseGeodeticRepresentation(BaseRepresentation): """Base geodetic representation.""" - attr_classes = {'lon': Longitude, - 'lat': Latitude, - 'height': u.Quantity} + attr_classes = {"lon": Longitude, "lat": Latitude, "height": u.Quantity} def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - if '_ellipsoid' in cls.__dict__: + if "_ellipsoid" in cls.__dict__: ELLIPSOIDS[cls._ellipsoid] = cls def __init__(self, lon, lat=None, height=None, copy=True): @@ -891,16 +916,18 @@ def __init__(self, lon, lat=None, height=None, copy=True): super().__init__(lon, lat, height, copy=copy) if not self.height.unit.is_equivalent(u.m): - raise u.UnitTypeError(f"{self.__class__.__name__} requires " - f"height with units of length.") + raise u.UnitTypeError( + f"{self.__class__.__name__} requires height with units of length." + ) def to_cartesian(self): """ Converts WGS84 geodetic coordinates to 3D rectangular (geocentric) cartesian coordinates. """ - xyz = erfa.gd2gc(getattr(erfa, self._ellipsoid), - self.lon, self.lat, self.height) + xyz = erfa.gd2gc( + getattr(erfa, self._ellipsoid), self.lon, self.lat, self.height + ) return CartesianRepresentation(xyz, xyz_axis=-1, copy=False) @classmethod @@ -909,8 +936,9 @@ def from_cartesian(cls, cart): Converts 3D rectangular cartesian coordinates (assumed geocentric) to WGS84 geodetic coordinates. """ - lon, lat, height = erfa.gc2gd(getattr(erfa, cls._ellipsoid), - cart.get_xyz(xyz_axis=-1)) + lon, lat, height = erfa.gc2gd( + getattr(erfa, cls._ellipsoid), cart.get_xyz(xyz_axis=-1) + ) return cls(lon, lat, height, copy=False) @@ -918,18 +946,18 @@ def from_cartesian(cls, cart): class WGS84GeodeticRepresentation(BaseGeodeticRepresentation): """Representation of points in WGS84 3D geodetic coordinates.""" - _ellipsoid = 'WGS84' + _ellipsoid = "WGS84" @format_doc(geodetic_base_doc) class WGS72GeodeticRepresentation(BaseGeodeticRepresentation): """Representation of points in WGS72 3D geodetic coordinates.""" - _ellipsoid = 'WGS72' + _ellipsoid = "WGS72" @format_doc(geodetic_base_doc) class GRS80GeodeticRepresentation(BaseGeodeticRepresentation): """Representation of points in GRS80 3D geodetic coordinates.""" - _ellipsoid = 'GRS80' + _ellipsoid = "GRS80" diff --git a/astropy/coordinates/earth_orientation.py b/astropy/coordinates/earth_orientation.py index db0636cbad4..bdb34cd510e 100644 --- a/astropy/coordinates/earth_orientation.py +++ b/astropy/coordinates/earth_orientation.py @@ -17,8 +17,8 @@ from .builtin_frames.utils import get_jd12 from .matrix_utilities import matrix_transpose, rotation_matrix -jd1950 = Time('B1950').jd -jd2000 = Time('J2000').jd +jd1950 = Time("B1950").jd +jd2000 = Time("J2000").jd def eccentricity(jd): @@ -42,7 +42,7 @@ def eccentricity(jd): """ T = (jd - jd1950) / 36525.0 - p = (-0.000000126, - 0.00004193, 0.01673011) + p = (-0.000000126, -0.00004193, 0.01673011) return np.polyval(p, T) @@ -71,7 +71,7 @@ def mean_lon_of_perigee(jd): p = (0.012, 1.65, 6190.67, 1015489.951) - return np.polyval(p, T) / 3600. + return np.polyval(p, T) / 3600.0 def obliquity(jd, algorithm=2006): @@ -109,7 +109,7 @@ def obliquity(jd, algorithm=2006): elif algorithm == 1980: return np.rad2deg(erfa.obl80(jd, 0)) else: - raise ValueError('invalid algorithm year for computing obliquity') + raise ValueError("invalid algorithm year for computing obliquity") def precession_matrix_Capitaine(fromepoch, toepoch): @@ -133,8 +133,8 @@ def precession_matrix_Capitaine(fromepoch, toepoch): Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351 """ # Multiply the two precession matrices (without frame bias) through J2000.0 - fromepoch_to_J2000 = matrix_transpose(erfa.bp06(*get_jd12(fromepoch, 'tt'))[1]) - J2000_to_toepoch = erfa.bp06(*get_jd12(toepoch, 'tt'))[1] + fromepoch_to_J2000 = matrix_transpose(erfa.bp06(*get_jd12(fromepoch, "tt"))[1]) + J2000_to_toepoch = erfa.bp06(*get_jd12(toepoch, "tt"))[1] return J2000_to_toepoch @ fromepoch_to_J2000 @@ -169,9 +169,9 @@ def _precession_matrix_besselian(epoch1, epoch2): theta = np.polyval(ptheta, dt) / 3600 return ( - rotation_matrix(-z, 'z') - @ rotation_matrix(theta, 'y') - @ rotation_matrix(-zeta, 'z') + rotation_matrix(-z, "z") + @ rotation_matrix(theta, "y") + @ rotation_matrix(-zeta, "z") ) @@ -220,4 +220,4 @@ def nutation_matrix(epoch): Seidelmann (ed), University Science Books (1992). """ # TODO: implement higher precision 2006/2000A model if requested/needed - return erfa.num00b(*get_jd12(epoch, 'tt')) + return erfa.num00b(*get_jd12(epoch, "tt")) diff --git a/astropy/coordinates/erfa_astrom.py b/astropy/coordinates/erfa_astrom.py index 2aa03449b10..f154af08122 100644 --- a/astropy/coordinates/erfa_astrom.py +++ b/astropy/coordinates/erfa_astrom.py @@ -27,15 +27,16 @@ class ErfaAstrom: - ''' + """ The default provider for astrometry values. A utility class to extract the necessary arguments for erfa functions from frame attributes, call the corresponding erfa functions and return the astrom object. - ''' + """ + @staticmethod def apco(frame_or_coord): - ''' + """ Wrapper for ``erfa.apco``, used in conversions AltAz <-> ICRS and CIRS <-> ICRS Parameters @@ -44,41 +45,52 @@ def apco(frame_or_coord): Frame or coordinate instance in the corresponding frame for which to calculate the calculate the astrom values. For this function, an AltAz or CIRS frame is expected. - ''' - lon, lat, height = frame_or_coord.location.to_geodetic('WGS84') + """ + lon, lat, height = frame_or_coord.location.to_geodetic("WGS84") obstime = frame_or_coord.obstime - jd1_tt, jd2_tt = get_jd12(obstime, 'tt') + jd1_tt, jd2_tt = get_jd12(obstime, "tt") xp, yp = get_polar_motion(obstime) sp = erfa.sp00(jd1_tt, jd2_tt) x, y, s = get_cip(jd1_tt, jd2_tt) - era = erfa.era00(*get_jd12(obstime, 'ut1')) + era = erfa.era00(*get_jd12(obstime, "ut1")) earth_pv, earth_heliocentric = prepare_earth_position_vel(obstime) # refraction constants - if hasattr(frame_or_coord, 'pressure'): + if hasattr(frame_or_coord, "pressure"): # this is an AltAz like frame. Calculate refraction refa, refb = erfa.refco( frame_or_coord.pressure.to_value(u.hPa), frame_or_coord.temperature.to_value(u.deg_C), frame_or_coord.relative_humidity.value, - frame_or_coord.obswl.to_value(u.micron) + frame_or_coord.obswl.to_value(u.micron), ) else: # This is not an AltAz frame, so don't bother computing refraction refa, refb = 0.0, 0.0 return erfa.apco( - jd1_tt, jd2_tt, earth_pv, earth_heliocentric, x, y, s, era, + jd1_tt, + jd2_tt, + earth_pv, + earth_heliocentric, + x, + y, + s, + era, lon.to_value(u.radian), lat.to_value(u.radian), height.to_value(u.m), - xp, yp, sp, refa, refb + xp, + yp, + sp, + refa, + refb, ) @staticmethod def apcs(frame_or_coord): - ''' + """ Wrapper for ``erfa.apcs``, used in conversions GCRS <-> ICRS Parameters @@ -87,18 +99,20 @@ def apcs(frame_or_coord): Frame or coordinate instance in the corresponding frame for which to calculate the calculate the astrom values. For this function, a GCRS frame is expected. - ''' - jd1_tt, jd2_tt = get_jd12(frame_or_coord.obstime, 'tt') + """ + jd1_tt, jd2_tt = get_jd12(frame_or_coord.obstime, "tt") obs_pv = pav2pv( frame_or_coord.obsgeoloc.get_xyz(xyz_axis=-1).value, - frame_or_coord.obsgeovel.get_xyz(xyz_axis=-1).value + frame_or_coord.obsgeovel.get_xyz(xyz_axis=-1).value, + ) + earth_pv, earth_heliocentric = prepare_earth_position_vel( + frame_or_coord.obstime ) - earth_pv, earth_heliocentric = prepare_earth_position_vel(frame_or_coord.obstime) return erfa.apcs(jd1_tt, jd2_tt, obs_pv, earth_pv, earth_heliocentric) @staticmethod def apio(frame_or_coord): - ''' + """ Slightly modified equivalent of ``erfa.apio``, used in conversions AltAz <-> CIRS. Since we use a topocentric CIRS frame, we have dropped the steps needed to calculate @@ -110,16 +124,16 @@ def apio(frame_or_coord): Frame or coordinate instance in the corresponding frame for which to calculate the calculate the astrom values. For this function, an AltAz frame is expected. - ''' + """ # Calculate erfa.apio input parameters. # TIO locator s' - sp = erfa.sp00(*get_jd12(frame_or_coord.obstime, 'tt')) + sp = erfa.sp00(*get_jd12(frame_or_coord.obstime, "tt")) # Earth rotation angle. - theta = erfa.era00(*get_jd12(frame_or_coord.obstime, 'ut1')) + theta = erfa.era00(*get_jd12(frame_or_coord.obstime, "ut1")) # Longitude and latitude in radians. - lon, lat, height = frame_or_coord.location.to_geodetic('WGS84') + lon, lat, height = frame_or_coord.location.to_geodetic("WGS84") elong = lon.to_value(u.radian) phi = lat.to_value(u.radian) @@ -130,47 +144,49 @@ def apio(frame_or_coord): astrom = np.zeros(frame_or_coord.obstime.shape, dtype=erfa.dt_eraASTROM) # Form the rotation matrix, CIRS to apparent [HA,Dec]. - r = (rotation_matrix(elong, 'z', unit=u.radian) - @ rotation_matrix(-yp, 'x', unit=u.radian) - @ rotation_matrix(-xp, 'y', unit=u.radian) - @ rotation_matrix(theta+sp, 'z', unit=u.radian)) + r = ( + rotation_matrix(elong, "z", unit=u.radian) + @ rotation_matrix(-yp, "x", unit=u.radian) + @ rotation_matrix(-xp, "y", unit=u.radian) + @ rotation_matrix(theta + sp, "z", unit=u.radian) + ) # Solve for local Earth rotation angle. a = r[..., 0, 0] b = r[..., 0, 1] eral = np.arctan2(b, a) - astrom['eral'] = eral + astrom["eral"] = eral # Solve for polar motion [X,Y] with respect to local meridian. c = r[..., 0, 2] - astrom['xpl'] = np.arctan2(c, np.sqrt(a*a+b*b)) + astrom["xpl"] = np.arctan2(c, np.sqrt(a * a + b * b)) a = r[..., 1, 2] b = r[..., 2, 2] - astrom['ypl'] = -np.arctan2(a, b) + astrom["ypl"] = -np.arctan2(a, b) # Adjusted longitude. - astrom['along'] = erfa.anpm(eral - theta) + astrom["along"] = erfa.anpm(eral - theta) # Functions of latitude. - astrom['sphi'] = np.sin(phi) - astrom['cphi'] = np.cos(phi) + astrom["sphi"] = np.sin(phi) + astrom["cphi"] = np.cos(phi) # Omit two steps that are zero for a geocentric observer: # Observer's geocentric position and velocity (m, m/s, CIRS). # Magnitude of diurnal aberration vector. # Refraction constants. - astrom['refa'], astrom['refb'] = erfa.refco( + astrom["refa"], astrom["refb"] = erfa.refco( frame_or_coord.pressure.to_value(u.hPa), frame_or_coord.temperature.to_value(u.deg_C), frame_or_coord.relative_humidity.value, - frame_or_coord.obswl.to_value(u.micron) + frame_or_coord.obswl.to_value(u.micron), ) return astrom class ErfaAstromInterpolator(ErfaAstrom): - ''' + """ A provider for astrometry values that does not call erfa for each individual timestamp but interpolates linearly between support points. @@ -205,40 +221,37 @@ class ErfaAstromInterpolator(ErfaAstrom): >>> crab = SkyCoord(ra='05h34m31.94s', dec='22d00m52.2s') >>> with erfa_astrom.set(ErfaAstromInterpolator(300 * u.s)): ... cirs = crab.transform_to(CIRS(obstime=obstime)) - ''' + """ @u.quantity_input(time_resolution=u.day) def __init__(self, time_resolution): if time_resolution.to_value(u.us) < 10: warnings.warn( - f'Using {self.__class__.__name__} with `time_resolution`' - ' below 10 microseconds might lead to numerical inaccuracies' - ' as the MJD-based interpolation is limited by floating point ' - ' precision to about a microsecond of precision', - AstropyWarning + f"Using {self.__class__.__name__} with `time_resolution`" + " below 10 microseconds might lead to numerical inaccuracies" + " as the MJD-based interpolation is limited by floating point " + " precision to about a microsecond of precision", + AstropyWarning, ) self.mjd_resolution = time_resolution.to_value(u.day) def _get_support_points(self, obstime): - ''' + """ Calculate support points for the interpolation. We divide the MJD by the time resolution (as single float64 values), and calculate ceil and floor. Then we take the unique and sorted values and scale back to MJD. This will create a sparse support for non-regular input obstimes. - ''' + """ mjd_scaled = np.ravel(obstime.mjd / self.mjd_resolution) # unique already does sorting - mjd_u = np.unique(np.concatenate([ - np.floor(mjd_scaled), - np.ceil(mjd_scaled), - ])) + mjd_u = np.unique(np.concatenate([np.floor(mjd_scaled), np.ceil(mjd_scaled)])) return Time( mjd_u * self.mjd_resolution, - format='mjd', + format="mjd", scale=obstime.scale, ) @@ -256,11 +269,9 @@ def _prepare_earth_position_vel(support, obstime): earth_pv = np.empty(obstime.shape, dtype=erfa.dt_pv) earth_heliocentric = np.empty(obstime.shape + (3,)) for dim in range(3): - for key in 'pv': + for key in "pv": earth_pv[key][..., dim] = np.interp( - obstime.mjd, - support.mjd, - pv_support[key][..., dim] + obstime.mjd, support.mjd, pv_support[key][..., dim] ) earth_heliocentric[..., dim] = np.interp( obstime.mjd, support.mjd, heliocentric_support[..., dim] @@ -276,12 +287,14 @@ def _get_c2i(support, obstime): Uses the coarser grid ``support`` to do the calculation, and interpolates onto the finer grid ``obstime``. """ - jd1_tt_support, jd2_tt_support = get_jd12(support, 'tt') + jd1_tt_support, jd2_tt_support = get_jd12(support, "tt") c2i_support = erfa.c2i06a(jd1_tt_support, jd2_tt_support) c2i = np.empty(obstime.shape + (3, 3)) for dim1 in range(3): for dim2 in range(3): - c2i[..., dim1, dim2] = np.interp(obstime.mjd, support.mjd, c2i_support[..., dim1, dim2]) + c2i[..., dim1, dim2] = np.interp( + obstime.mjd, support.mjd, c2i_support[..., dim1, dim2] + ) return c2i @staticmethod @@ -292,7 +305,7 @@ def _get_cip(support, obstime): Uses the coarser grid ``support`` to do the calculation, and interpolates onto the finer grid ``obstime``. """ - jd1_tt_support, jd2_tt_support = get_jd12(support, 'tt') + jd1_tt_support, jd2_tt_support = get_jd12(support, "tt") cip_support = get_cip(jd1_tt_support, jd2_tt_support) return tuple( np.interp(obstime.mjd, support.mjd, cip_component) @@ -314,7 +327,7 @@ def _get_polar_motion(support, obstime): ) def apco(self, frame_or_coord): - ''' + """ Wrapper for ``erfa.apco``, used in conversions AltAz <-> ICRS and CIRS <-> ICRS Parameters @@ -323,44 +336,57 @@ def apco(self, frame_or_coord): Frame or coordinate instance in the corresponding frame for which to calculate the calculate the astrom values. For this function, an AltAz or CIRS frame is expected. - ''' - lon, lat, height = frame_or_coord.location.to_geodetic('WGS84') + """ + lon, lat, height = frame_or_coord.location.to_geodetic("WGS84") obstime = frame_or_coord.obstime support = self._get_support_points(obstime) - jd1_tt, jd2_tt = get_jd12(obstime, 'tt') + jd1_tt, jd2_tt = get_jd12(obstime, "tt") # get the position and velocity arrays for the observatory. Need to # have xyz in last dimension, and pos/vel in one-but-last. - earth_pv, earth_heliocentric = self._prepare_earth_position_vel(support, obstime) + earth_pv, earth_heliocentric = self._prepare_earth_position_vel( + support, obstime + ) xp, yp = self._get_polar_motion(support, obstime) sp = erfa.sp00(jd1_tt, jd2_tt) x, y, s = self._get_cip(support, obstime) - era = erfa.era00(*get_jd12(obstime, 'ut1')) + era = erfa.era00(*get_jd12(obstime, "ut1")) # refraction constants - if hasattr(frame_or_coord, 'pressure'): + if hasattr(frame_or_coord, "pressure"): # an AltAz like frame. Include refraction refa, refb = erfa.refco( frame_or_coord.pressure.to_value(u.hPa), frame_or_coord.temperature.to_value(u.deg_C), frame_or_coord.relative_humidity.value, - frame_or_coord.obswl.to_value(u.micron) + frame_or_coord.obswl.to_value(u.micron), ) else: # a CIRS like frame - no refraction refa, refb = 0.0, 0.0 return erfa.apco( - jd1_tt, jd2_tt, earth_pv, earth_heliocentric, x, y, s, era, + jd1_tt, + jd2_tt, + earth_pv, + earth_heliocentric, + x, + y, + s, + era, lon.to_value(u.radian), lat.to_value(u.radian), height.to_value(u.m), - xp, yp, sp, refa, refb + xp, + yp, + sp, + refa, + refb, ) def apcs(self, frame_or_coord): - ''' + """ Wrapper for ``erfa.apci``, used in conversions GCRS <-> ICRS Parameters @@ -369,19 +395,21 @@ def apcs(self, frame_or_coord): Frame or coordinate instance in the corresponding frame for which to calculate the calculate the astrom values. For this function, a GCRS frame is expected. - ''' + """ obstime = frame_or_coord.obstime support = self._get_support_points(obstime) # get the position and velocity arrays for the observatory. Need to # have xyz in last dimension, and pos/vel in one-but-last. - earth_pv, earth_heliocentric = self._prepare_earth_position_vel(support, obstime) + earth_pv, earth_heliocentric = self._prepare_earth_position_vel( + support, obstime + ) pv = pav2pv( frame_or_coord.obsgeoloc.get_xyz(xyz_axis=-1).value, - frame_or_coord.obsgeovel.get_xyz(xyz_axis=-1).value + frame_or_coord.obsgeovel.get_xyz(xyz_axis=-1).value, ) - jd1_tt, jd2_tt = get_jd12(obstime, 'tt') + jd1_tt, jd2_tt = get_jd12(obstime, "tt") return erfa.apcs(jd1_tt, jd2_tt, pv, earth_pv, earth_heliocentric) @@ -396,5 +424,5 @@ class erfa_astrom(ScienceState): @classmethod def validate(cls, value): if not isinstance(value, ErfaAstrom): - raise TypeError(f'Must be an instance of {ErfaAstrom!r}') + raise TypeError(f"Must be an instance of {ErfaAstrom!r}") return value diff --git a/astropy/coordinates/errors.py b/astropy/coordinates/errors.py index fc7aaa829fd..89c99a10e01 100644 --- a/astropy/coordinates/errors.py +++ b/astropy/coordinates/errors.py @@ -1,14 +1,22 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -''' This module defines custom errors and exceptions used in astropy.coordinates. -''' +""" This module defines custom errors and exceptions used in astropy.coordinates. +""" from astropy.utils.exceptions import AstropyWarning -__all__ = ['RangeError', 'BoundsError', 'IllegalHourError', - 'IllegalMinuteError', 'IllegalSecondError', 'ConvertError', - 'IllegalHourWarning', 'IllegalMinuteWarning', 'IllegalSecondWarning', - 'UnknownSiteException'] +__all__ = [ + "RangeError", + "BoundsError", + "IllegalHourError", + "IllegalMinuteError", + "IllegalSecondError", + "ConvertError", + "IllegalHourWarning", + "IllegalMinuteWarning", + "IllegalSecondWarning", + "UnknownSiteException", +] class RangeError(ValueError): @@ -39,11 +47,15 @@ class IllegalHourError(RangeError): if not 0 <= hr < 24: raise IllegalHourError(hour) """ + def __init__(self, hour): self.hour = hour def __str__(self): - return f"An invalid value for 'hours' was found ('{self.hour}'); must be in the range [0,24)." + return ( + f"An invalid value for 'hours' was found ('{self.hour}'); must be in the" + " range [0,24)." + ) class IllegalHourWarning(AstropyWarning): @@ -54,14 +66,17 @@ class IllegalHourWarning(AstropyWarning): ---------- hour : int, float """ + def __init__(self, hour, alternativeactionstr=None): self.hour = hour self.alternativeactionstr = alternativeactionstr def __str__(self): - message = f"'hour' was found to be '{self.hour}', which is not in range (-24, 24)." + message = ( + f"'hour' was found to be '{self.hour}', which is not in range (-24, 24)." + ) if self.alternativeactionstr is not None: - message += ' ' + self.alternativeactionstr + message += " " + self.alternativeactionstr return message @@ -82,11 +97,15 @@ class IllegalMinuteError(RangeError): raise IllegalMinuteError(minute) """ + def __init__(self, minute): self.minute = minute def __str__(self): - return f"An invalid value for 'minute' was found ('{self.minute}'); should be in the range [0,60)." + return ( + f"An invalid value for 'minute' was found ('{self.minute}'); should be in" + " the range [0,60)." + ) class IllegalMinuteWarning(AstropyWarning): @@ -97,14 +116,17 @@ class IllegalMinuteWarning(AstropyWarning): ---------- minute : int, float """ + def __init__(self, minute, alternativeactionstr=None): self.minute = minute self.alternativeactionstr = alternativeactionstr def __str__(self): - message = f"'minute' was found to be '{self.minute}', which is not in range [0,60)." + message = ( + f"'minute' was found to be '{self.minute}', which is not in range [0,60)." + ) if self.alternativeactionstr is not None: - message += ' ' + self.alternativeactionstr + message += " " + self.alternativeactionstr return message @@ -124,11 +146,15 @@ class IllegalSecondError(RangeError): if not 0 <= sec < 60: raise IllegalSecondError(second) """ + def __init__(self, second): self.second = second def __str__(self): - return f"An invalid value for 'second' was found ('{self.second}'); should be in the range [0,60)." + return ( + f"An invalid value for 'second' was found ('{self.second}'); should be in" + " the range [0,60)." + ) class IllegalSecondWarning(AstropyWarning): @@ -139,14 +165,17 @@ class IllegalSecondWarning(AstropyWarning): ---------- second : int, float """ + def __init__(self, second, alternativeactionstr=None): self.second = second self.alternativeactionstr = alternativeactionstr def __str__(self): - message = f"'second' was found to be '{self.second}', which is not in range [0,60)." + message = ( + f"'second' was found to be '{self.second}', which is not in range [0,60)." + ) if self.alternativeactionstr is not None: - message += ' ' + self.alternativeactionstr + message += " " + self.alternativeactionstr return message @@ -165,7 +194,9 @@ class ConvertError(Exception): class UnknownSiteException(KeyError): def __init__(self, site, attribute, close_names=None): - message = f"Site '{site}' not in database. Use {attribute} to see available sites." + message = ( + f"Site '{site}' not in database. Use {attribute} to see available sites." + ) if close_names: message += " Did you mean one of: '{}'?'".format("', '".join(close_names)) self.site = site diff --git a/astropy/coordinates/funcs.py b/astropy/coordinates/funcs.py index a229def6d4a..d5d33f70182 100644 --- a/astropy/coordinates/funcs.py +++ b/astropy/coordinates/funcs.py @@ -24,8 +24,14 @@ from .representation import CartesianRepresentation, SphericalRepresentation from .sky_coordinate import SkyCoord -__all__ = ['cartesian_to_spherical', 'spherical_to_cartesian', 'get_sun', - 'get_constellation', 'concatenate_representations', 'concatenate'] +__all__ = [ + "cartesian_to_spherical", + "spherical_to_cartesian", + "get_sun", + "get_constellation", + "concatenate_representations", + "concatenate", +] def cartesian_to_spherical(x, y, z): @@ -63,11 +69,11 @@ def cartesian_to_spherical(x, y, z): lon : `~astropy.units.Quantity` ['angle'] The longitude in radians """ - if not hasattr(x, 'unit'): + if not hasattr(x, "unit"): x = x * u.dimensionless_unscaled - if not hasattr(y, 'unit'): + if not hasattr(y, "unit"): y = y * u.dimensionless_unscaled - if not hasattr(z, 'unit'): + if not hasattr(z, "unit"): z = z * u.dimensionless_unscaled cart = CartesianRepresentation(x, y, z) @@ -110,11 +116,11 @@ def spherical_to_cartesian(r, lat, lon): The third cartesian coordinate. """ - if not hasattr(r, 'unit'): + if not hasattr(r, "unit"): r = r * u.dimensionless_unscaled - if not hasattr(lat, 'unit'): + if not hasattr(lat, "unit"): lat = lat * u.radian - if not hasattr(lon, 'unit'): + if not hasattr(lon, "unit"): lon = lon * u.radian sph = SphericalRepresentation(distance=r, lat=lat, lon=lon) @@ -148,24 +154,27 @@ def get_sun(time): 250 km over the 1000-3000. """ - earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(time, 'tdb')) + earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(time, "tdb")) # We have to manually do aberration because we're outputting directly into # GCRS - earth_p = earth_pv_helio['p'] - earth_v = earth_pv_bary['v'] + earth_p = earth_pv_helio["p"] + earth_v = earth_pv_bary["v"] # convert barycentric velocity to units of c, but keep as array for passing in to erfa - earth_v /= c.to_value(u.au/u.d) + earth_v /= c.to_value(u.au / u.d) dsun = np.sqrt(np.sum(earth_p**2, axis=-1)) - invlorentz = (1-np.sum(earth_v**2, axis=-1))**0.5 - properdir = erfa.ab(earth_p/dsun.reshape(dsun.shape + (1,)), - -earth_v, dsun, invlorentz) - - cartrep = CartesianRepresentation(x=-dsun*properdir[..., 0] * u.AU, - y=-dsun*properdir[..., 1] * u.AU, - z=-dsun*properdir[..., 2] * u.AU) + invlorentz = (1 - np.sum(earth_v**2, axis=-1)) ** 0.5 + properdir = erfa.ab( + earth_p / dsun.reshape(dsun.shape + (1,)), -earth_v, dsun, invlorentz + ) + + cartrep = CartesianRepresentation( + x=-dsun * properdir[..., 0] * u.AU, + y=-dsun * properdir[..., 1] * u.AU, + z=-dsun * properdir[..., 2] * u.AU, + ) return SkyCoord(cartrep, frame=GCRS(obstime=time)) @@ -173,7 +182,7 @@ def get_sun(time): _constellation_data = {} -def get_constellation(coord, short_name=False, constellation_list='iau'): +def get_constellation(coord, short_name=False, constellation_list="iau"): """ Determines the constellation(s) a given coordinate object contains. @@ -202,23 +211,26 @@ def get_constellation(coord, short_name=False, constellation_list='iau'): constellations, as tabulated by `Roman 1987 `_. """ - if constellation_list != 'iau': + if constellation_list != "iau": raise ValueError("only 'iau' us currently supported for constellation_list") # read the data files and cache them if they haven't been already if not _constellation_data: - cdata = data.get_pkg_data_contents('data/constellation_data_roman87.dat') - ctable = ascii.read(cdata, names=['ral', 'rau', 'decl', 'name']) - cnames = data.get_pkg_data_contents('data/constellation_names.dat', encoding='UTF8') - cnames_short_to_long = {l[:3]: l[4:] for l in cnames.split('\n') - if not l.startswith('#')} - cnames_long = np.array([cnames_short_to_long[nm] for nm in ctable['name']]) - - _constellation_data['ctable'] = ctable - _constellation_data['cnames_long'] = cnames_long + cdata = data.get_pkg_data_contents("data/constellation_data_roman87.dat") + ctable = ascii.read(cdata, names=["ral", "rau", "decl", "name"]) + cnames = data.get_pkg_data_contents( + "data/constellation_names.dat", encoding="UTF8" + ) + cnames_short_to_long = { + l[:3]: l[4:] for l in cnames.split("\n") if not l.startswith("#") + } + cnames_long = np.array([cnames_short_to_long[nm] for nm in ctable["name"]]) + + _constellation_data["ctable"] = ctable + _constellation_data["cnames_long"] = cnames_long else: - ctable = _constellation_data['ctable'] - cnames_long = _constellation_data['cnames_long'] + ctable = _constellation_data["ctable"] + cnames_long = _constellation_data["cnames_long"] isscalar = coord.isscalar @@ -229,8 +241,8 @@ def get_constellation(coord, short_name=False, constellation_list='iau'): # models aren't precisely calibrated back to then. But it's plenty # sufficient for constellations with warnings.catch_warnings(): - warnings.simplefilter('ignore', erfa.ErfaWarning) - constel_coord = coord.transform_to(PrecessedGeocentric(equinox='B1875')) + warnings.simplefilter("ignore", erfa.ErfaWarning) + constel_coord = coord.transform_to(PrecessedGeocentric(equinox="B1875")) if isscalar: rah = constel_coord.ra.ravel().hour decd = constel_coord.dec.ravel().deg @@ -242,16 +254,18 @@ def get_constellation(coord, short_name=False, constellation_list='iau'): notided = constellidx == -1 # should be all for i, row in enumerate(ctable): - msk = (row['ral'] < rah) & (rah < row['rau']) & (decd > row['decl']) + msk = (row["ral"] < rah) & (rah < row["rau"]) & (decd > row["decl"]) constellidx[notided & msk] = i notided = constellidx == -1 if np.sum(notided) == 0: break else: - raise ValueError(f'Could not find constellation for coordinates {constel_coord[notided]}') + raise ValueError( + f"Could not find constellation for coordinates {constel_coord[notided]}" + ) if short_name: - names = ctable['name'][constellidx] + names = ctable["name"][constellidx] else: names = cnames_long[constellidx] @@ -262,7 +276,7 @@ def get_constellation(coord, short_name=False, constellation_list='iau'): def _concatenate_components(reps_difs, names): - """ Helper function for the concatenate function below. Gets and + """Helper function for the concatenate function below. Gets and concatenates all of the individual components for an iterable of representations or differentials. """ @@ -300,39 +314,42 @@ def concatenate_representations(reps): """ if not isinstance(reps, (Sequence, np.ndarray)): - raise TypeError('Input must be a list or iterable of representation ' - 'objects.') + raise TypeError("Input must be a list or iterable of representation objects.") # First, validate that the representations are the same, and # concatenate all of the positional data: rep_type = type(reps[0]) if any(type(r) != rep_type for r in reps): - raise TypeError('Input representations must all have the same type.') + raise TypeError("Input representations must all have the same type.") # Construct the new representation with the concatenated data from the # representations passed in - values = _concatenate_components(reps, - rep_type.attr_classes.keys()) + values = _concatenate_components(reps, rep_type.attr_classes.keys()) new_rep = rep_type(*values) - has_diff = any('s' in rep.differentials for rep in reps) - if has_diff and any('s' not in rep.differentials for rep in reps): - raise ValueError('Input representations must either all contain ' - 'differentials, or not contain differentials.') + has_diff = any("s" in rep.differentials for rep in reps) + if has_diff and any("s" not in rep.differentials for rep in reps): + raise ValueError( + "Input representations must either all contain " + "differentials, or not contain differentials." + ) if has_diff: - dif_type = type(reps[0].differentials['s']) - - if any('s' not in r.differentials or - type(r.differentials['s']) != dif_type - for r in reps): - raise TypeError('All input representations must have the same ' - 'differential type.') - - values = _concatenate_components([r.differentials['s'] for r in reps], - dif_type.attr_classes.keys()) + dif_type = type(reps[0].differentials["s"]) + + if any( + "s" not in r.differentials or type(r.differentials["s"]) != dif_type + for r in reps + ): + raise TypeError( + "All input representations must have the same differential type." + ) + + values = _concatenate_components( + [r.differentials["s"] for r in reps], dif_type.attr_classes.keys() + ) new_dif = dif_type(*values) - new_rep = new_rep.with_differentials({'s': new_dif}) + new_rep = new_rep.with_differentials({"s": new_dif}) return new_rep @@ -358,18 +375,20 @@ def concatenate(coords): A single sky coordinate with its data set to the concatenation of all the elements in ``coords`` """ - if getattr(coords, 'isscalar', False) or not isiterable(coords): - raise TypeError('The argument to concatenate must be iterable') + if getattr(coords, "isscalar", False) or not isiterable(coords): + raise TypeError("The argument to concatenate must be iterable") scs = [SkyCoord(coord, copy=False) for coord in coords] # Check that all frames are equivalent for sc in scs[1:]: if not sc.is_equivalent_frame(scs[0]): - raise ValueError("All inputs must have equivalent frames: " - "{} != {}".format(sc, scs[0])) + raise ValueError( + f"All inputs must have equivalent frames: {sc} != {scs[0]}" + ) # TODO: this can be changed to SkyCoord.from_representation() for a speed # boost when we switch to using classmethods - return SkyCoord(concatenate_representations([c.data for c in coords]), - frame=scs[0].frame) + return SkyCoord( + concatenate_representations([c.data for c in coords]), frame=scs[0].frame + ) diff --git a/astropy/coordinates/jparser.py b/astropy/coordinates/jparser.py index 29d4387a882..c6833fc018f 100644 --- a/astropy/coordinates/jparser.py +++ b/astropy/coordinates/jparser.py @@ -10,17 +10,17 @@ import astropy.units as u from astropy.coordinates import SkyCoord -RA_REGEX = r'()([0-2]\d)([0-5]\d)([0-5]\d)\.?(\d{0,3})' -DEC_REGEX = r'([+-])(\d{1,2})([0-5]\d)([0-5]\d)\.?(\d{0,3})' -JCOORD_REGEX = '(.*?J)' + RA_REGEX + DEC_REGEX +RA_REGEX = r"()([0-2]\d)([0-5]\d)([0-5]\d)\.?(\d{0,3})" +DEC_REGEX = r"([+-])(\d{1,2})([0-5]\d)([0-5]\d)\.?(\d{0,3})" +JCOORD_REGEX = "(.*?J)" + RA_REGEX + DEC_REGEX JPARSER = re.compile(JCOORD_REGEX) def _sexagesimal(g): # convert matched regex groups to sexigesimal array sign, h, m, s, frac = g - sign = -1 if (sign == '-') else 1 - s = '.'.join((s, frac)) + sign = -1 if (sign == "-") else 1 + s = ".".join((s, frac)) return sign * np.array([h, m, s], float) @@ -29,12 +29,12 @@ def search(name, raise_=False): # extract the coordinate data from name match = JPARSER.search(name) if match is None and raise_: - raise ValueError('No coordinate match found!') + raise ValueError("No coordinate match found!") return match def to_ra_dec_angles(name): - """get RA in hourangle and DEC in degrees by parsing name """ + """get RA in hourangle and DEC in degrees by parsing name""" groups = search(name, True).groups() prefix, hms, dms = np.split(groups, [1, 6]) ra = (_sexagesimal(hms) / (1, 60, 60 * 60) * u.hourangle).sum() @@ -42,7 +42,7 @@ def to_ra_dec_angles(name): return ra, dec -def to_skycoord(name, frame='icrs'): +def to_skycoord(name, frame="icrs"): """Convert to `name` to `SkyCoords` object""" return SkyCoord(*to_ra_dec_angles(name), frame=frame) @@ -61,4 +61,4 @@ def shorten(name): shortName: str """ match = search(name) - return ''.join(match.group(1, 3, 4, 7, 8, 9)) + return "".join(match.group(1, 3, 4, 7, 8, 9)) diff --git a/astropy/coordinates/matching.py b/astropy/coordinates/matching.py index 435e37299de..aa219ab69ef 100644 --- a/astropy/coordinates/matching.py +++ b/astropy/coordinates/matching.py @@ -12,11 +12,17 @@ from .representation import UnitSphericalRepresentation from .sky_coordinate import SkyCoord -__all__ = ['match_coordinates_3d', 'match_coordinates_sky', 'search_around_3d', - 'search_around_sky'] +__all__ = [ + "match_coordinates_3d", + "match_coordinates_sky", + "search_around_3d", + "search_around_sky", +] -def match_coordinates_3d(matchcoord, catalogcoord, nthneighbor=1, storekdtree='kdtree_3d'): +def match_coordinates_3d( + matchcoord, catalogcoord, nthneighbor=1, storekdtree="kdtree_3d" +): """ Finds the nearest 3-dimensional matches of a coordinate or coordinates in a set of catalog coordinates. @@ -63,8 +69,9 @@ def match_coordinates_3d(matchcoord, catalogcoord, nthneighbor=1, storekdtree='k or it will fail. """ if catalogcoord.isscalar or len(catalogcoord) < 1: - raise ValueError('The catalog for coordinate matching cannot be a ' - 'scalar or length-0.') + raise ValueError( + "The catalog for coordinate matching cannot be a scalar or length-0." + ) kdt = _get_cartesian_kdtree(catalogcoord, storekdtree) @@ -89,10 +96,16 @@ def match_coordinates_3d(matchcoord, catalogcoord, nthneighbor=1, storekdtree='k idx = idx[:, -1] sep2d = catalogcoord[idx].separation(matchcoord) - return idx.reshape(matchxyz.shape[1:]), sep2d, dist.reshape(matchxyz.shape[1:]) * catunit + return ( + idx.reshape(matchxyz.shape[1:]), + sep2d, + dist.reshape(matchxyz.shape[1:]) * catunit, + ) -def match_coordinates_sky(matchcoord, catalogcoord, nthneighbor=1, storekdtree='kdtree_sky'): +def match_coordinates_sky( + matchcoord, catalogcoord, nthneighbor=1, storekdtree="kdtree_sky" +): """ Finds the nearest on-sky matches of a coordinate or coordinates in a set of catalog coordinates. @@ -141,8 +154,9 @@ def match_coordinates_sky(matchcoord, catalogcoord, nthneighbor=1, storekdtree=' or it will fail. """ if catalogcoord.isscalar or len(catalogcoord) < 1: - raise ValueError('The catalog for coordinate matching cannot be a ' - 'scalar or length-0.') + raise ValueError( + "The catalog for coordinate matching cannot be a scalar or length-0." + ) # send to catalog frame if isinstance(matchcoord, SkyCoord): @@ -162,11 +176,15 @@ def match_coordinates_sky(matchcoord, catalogcoord, nthneighbor=1, storekdtree=' # it's based on UnitSphericalRepresentation. storekdtree = catalogcoord.cache.get(storekdtree, storekdtree) - idx, sep2d, sep3d = match_coordinates_3d(newmatch_u, newcat_u, nthneighbor, storekdtree) + idx, sep2d, sep3d = match_coordinates_3d( + newmatch_u, newcat_u, nthneighbor, storekdtree + ) # sep3d is *wrong* above, because the distance information was removed, # unless one of the catalogs doesn't have a real distance - if not (isinstance(catalogcoord.data, UnitSphericalRepresentation) or - isinstance(newmatch.data, UnitSphericalRepresentation)): + if not ( + isinstance(catalogcoord.data, UnitSphericalRepresentation) + or isinstance(newmatch.data, UnitSphericalRepresentation) + ): sep3d = catalogcoord[idx].separation_3d(newmatch) # update the kdtree on the actual passed-in coordinate @@ -174,12 +192,12 @@ def match_coordinates_sky(matchcoord, catalogcoord, nthneighbor=1, storekdtree=' catalogcoord.cache[storekdtree] = newcat_u.cache[storekdtree] elif storekdtree is True: # the old backwards-compatible name - catalogcoord.cache['kdtree'] = newcat_u.cache['kdtree'] + catalogcoord.cache["kdtree"] = newcat_u.cache["kdtree"] return idx, sep2d, sep3d -def search_around_3d(coords1, coords2, distlimit, storekdtree='kdtree_3d'): +def search_around_3d(coords1, coords2, distlimit, storekdtree="kdtree_3d"): """ Searches for pairs of points that are at least as close as a specified distance in 3D space. @@ -235,20 +253,24 @@ def search_around_3d(coords1, coords2, distlimit, storekdtree='kdtree_3d'): release. """ if not distlimit.isscalar: - raise ValueError('distlimit must be a scalar in search_around_3d') + raise ValueError("distlimit must be a scalar in search_around_3d") if coords1.isscalar or coords2.isscalar: - raise ValueError('One of the inputs to search_around_3d is a scalar. ' - 'search_around_3d is intended for use with array ' - 'coordinates, not scalars. Instead, use ' - '``coord1.separation_3d(coord2) < distlimit`` to find ' - 'the coordinates near a scalar coordinate.') + raise ValueError( + "One of the inputs to search_around_3d is a scalar. search_around_3d is" + " intended for use with array coordinates, not scalars. Instead, use" + " ``coord1.separation_3d(coord2) < distlimit`` to find the coordinates near" + " a scalar coordinate." + ) if len(coords1) == 0 or len(coords2) == 0: # Empty array input: return empty match - return (np.array([], dtype=int), np.array([], dtype=int), - Angle([], u.deg), - u.Quantity([], coords1.distance.unit)) + return ( + np.array([], dtype=int), + np.array([], dtype=int), + Angle([], u.deg), + u.Quantity([], coords1.distance.unit), + ) kdt2 = _get_cartesian_kdtree(coords2, storekdtree) cunit = coords2.cartesian.x.unit @@ -282,7 +304,7 @@ def search_around_3d(coords1, coords2, distlimit, storekdtree='kdtree_3d'): return idxs1, idxs2, d2ds, d3ds -def search_around_sky(coords1, coords2, seplimit, storekdtree='kdtree_sky'): +def search_around_sky(coords1, coords2, seplimit, storekdtree="kdtree_sky"): """ Searches for pairs of points that have an angular separation at least as close as a specified angle. @@ -335,14 +357,15 @@ def search_around_sky(coords1, coords2, seplimit, storekdtree='kdtree_sky'): release. """ if not seplimit.isscalar: - raise ValueError('seplimit must be a scalar in search_around_sky') + raise ValueError("seplimit must be a scalar in search_around_sky") if coords1.isscalar or coords2.isscalar: - raise ValueError('One of the inputs to search_around_sky is a scalar. ' - 'search_around_sky is intended for use with array ' - 'coordinates, not scalars. Instead, use ' - '``coord1.separation(coord2) < seplimit`` to find the ' - 'coordinates near a scalar coordinate.') + raise ValueError( + "One of the inputs to search_around_sky is a scalar. search_around_sky is" + " intended for use with array coordinates, not scalars. Instead, use" + " ``coord1.separation(coord2) < seplimit`` to find the coordinates near a" + " scalar coordinate." + ) if len(coords1) == 0 or len(coords2) == 0: # Empty array input: return empty match @@ -350,9 +373,12 @@ def search_around_sky(coords1, coords2, seplimit, storekdtree='kdtree_sky'): distunit = u.dimensionless_unscaled else: distunit = coords1.distance.unit - return (np.array([], dtype=int), np.array([], dtype=int), - Angle([], u.deg), - u.Quantity([], distunit)) + return ( + np.array([], dtype=int), + np.array([], dtype=int), + Angle([], u.deg), + u.Quantity([], distunit), + ) # we convert coord1 to match coord2's frame. We do it this way # so that if the conversion does happen, the KD tree of coord2 at least gets @@ -376,7 +402,7 @@ def search_around_sky(coords1, coords2, seplimit, storekdtree='kdtree_sky'): kdt2 = _get_cartesian_kdtree(ucoords2, storekdtree) if storekdtree: # save the KD-Tree in coords2, *not* ucoords2 - coords2.cache['kdtree' if storekdtree is True else storekdtree] = kdt2 + coords2.cache["kdtree" if storekdtree is True else storekdtree] = kdt2 # this is the *cartesian* 3D distance that corresponds to the given angle r = (2 * np.sin(Angle(seplimit) / 2.0)).value @@ -409,7 +435,7 @@ def search_around_sky(coords1, coords2, seplimit, storekdtree='kdtree_sky'): return idxs1, idxs2, d2ds, d3ds -def _get_cartesian_kdtree(coord, attrname_or_kdt='kdtree', forceunit=None): +def _get_cartesian_kdtree(coord, attrname_or_kdt="kdtree", forceunit=None): """ This is a utility function to retrieve (and build/cache, if necessary) a 3D cartesian KD-Tree from various sorts of astropy coordinate objects. @@ -437,29 +463,35 @@ def _get_cartesian_kdtree(coord, attrname_or_kdt='kdtree', forceunit=None): # without scipy this will immediately fail from scipy import spatial + try: KDTree = spatial.cKDTree except Exception: - warn('C-based KD tree not found, falling back on (much slower) ' - 'python implementation') + warn( + "C-based KD tree not found, falling back on (much slower) " + "python implementation" + ) KDTree = spatial.KDTree if attrname_or_kdt is True: # backwards compatibility for pre v0.4 - attrname_or_kdt = 'kdtree' + attrname_or_kdt = "kdtree" # figure out where any cached KDTree might be if isinstance(attrname_or_kdt, str): kdt = coord.cache.get(attrname_or_kdt, None) if kdt is not None and not isinstance(kdt, KDTree): - raise TypeError(f'The `attrname_or_kdt` "{attrname_or_kdt}" is not a scipy KD tree!') + raise TypeError( + f'The `attrname_or_kdt` "{attrname_or_kdt}" is not a scipy KD tree!' + ) elif isinstance(attrname_or_kdt, KDTree): kdt = attrname_or_kdt attrname_or_kdt = None elif not attrname_or_kdt: kdt = None else: - raise TypeError('Invalid `attrname_or_kdt` argument for KD-Tree:' + - str(attrname_or_kdt)) + raise TypeError( + "Invalid `attrname_or_kdt` argument for KD-Tree:" + str(attrname_or_kdt) + ) if kdt is None: # need to build the cartesian KD-tree for the catalog diff --git a/astropy/coordinates/matrix_utilities.py b/astropy/coordinates/matrix_utilities.py index 7084b0d5a77..d7f1ae1813b 100644 --- a/astropy/coordinates/matrix_utilities.py +++ b/astropy/coordinates/matrix_utilities.py @@ -41,7 +41,7 @@ def matrix_transpose(matrix): return matrix.swapaxes(-2, -1) -def rotation_matrix(angle, axis='z', unit=None): +def rotation_matrix(angle, axis="z", unit=None): """ Generate matrices for rotation by some angle around some axis. @@ -77,12 +77,15 @@ def rotation_matrix(angle, axis='z', unit=None): # use optimized implementations for x/y/z try: - i = 'xyz'.index(axis) + i = "xyz".index(axis) except TypeError: axis = np.asarray(axis) axis = axis / np.sqrt((axis * axis).sum(axis=-1, keepdims=True)) - R = (axis[..., np.newaxis] * axis[..., np.newaxis, :] * - (1. - c)[..., np.newaxis, np.newaxis]) + R = ( + axis[..., np.newaxis] + * axis[..., np.newaxis, :] + * (1.0 - c)[..., np.newaxis, np.newaxis] + ) for i in range(0, 3): R[..., i, i] += c @@ -94,8 +97,8 @@ def rotation_matrix(angle, axis='z', unit=None): else: a1 = (i + 1) % 3 a2 = (i + 2) % 3 - R = np.zeros(getattr(angle, 'shape', ()) + (3, 3)) - R[..., i, i] = 1. + R = np.zeros(getattr(angle, "shape", ()) + (3, 3)) + R[..., i, i] = 1.0 R[..., a1, a1] = c R[..., a1, a2] = s R[..., a2, a1] = -s @@ -122,15 +125,14 @@ def angle_axis(matrix): """ m = np.asanyarray(matrix) if m.shape[-2:] != (3, 3): - raise ValueError('matrix is not 3x3') + raise ValueError("matrix is not 3x3") axis = np.zeros(m.shape[:-1]) axis[..., 0] = m[..., 2, 1] - m[..., 1, 2] axis[..., 1] = m[..., 0, 2] - m[..., 2, 0] axis[..., 2] = m[..., 1, 0] - m[..., 0, 1] r = np.sqrt((axis * axis).sum(-1, keepdims=True)) - angle = np.arctan2(r[..., 0], - m[..., 0, 0] + m[..., 1, 1] + m[..., 2, 2] - 1.) + angle = np.arctan2(r[..., 0], m[..., 0, 0] + m[..., 1, 1] + m[..., 2, 2] - 1.0) return Angle(angle, u.radian), -axis / r @@ -157,8 +159,9 @@ def is_O3(matrix): """ # matrix is in O(3) (rotations, proper and improper). I = np.identity(matrix.shape[-1]) - is_o3 = np.all(np.isclose(matrix @ matrix.swapaxes(-2, -1), I, atol=1e-15), - axis=(-2, -1)) + is_o3 = np.all( + np.isclose(matrix @ matrix.swapaxes(-2, -1), I, atol=1e-15), axis=(-2, -1) + ) return is_o3 diff --git a/astropy/coordinates/name_resolve.py b/astropy/coordinates/name_resolve.py index b33c6a6ed57..a466842d347 100644 --- a/astropy/coordinates/name_resolve.py +++ b/astropy/coordinates/name_resolve.py @@ -31,8 +31,11 @@ class sesame_url(ScienceState): """ The URL(s) to Sesame's web-queryable database. """ - _value = ["http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/", - "http://vizier.cfa.harvard.edu/viz-bin/nph-sesame/"] + + _value = [ + "http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/", + "http://vizier.cfa.harvard.edu/viz-bin/nph-sesame/", + ] @classmethod def validate(cls, value): @@ -47,11 +50,12 @@ class sesame_database(ScienceState): subpackage. Default is to search all databases, but this can be 'all', 'simbad', 'ned', or 'vizier'. """ - _value = 'all' + + _value = "all" @classmethod def validate(cls, value): - if value not in ['all', 'simbad', 'ned', 'vizier']: + if value not in ["all", "simbad", "ned", "vizier"]: raise ValueError(f"Unknown database '{value}'") return value @@ -132,6 +136,7 @@ def get_icrs_coordinates(name, parse=False, cache=False): # Do this first since it may be much faster than doing the sesame query if parse: from . import jparser + if jparser.search(name): return jparser.to_skycoord(name) else: @@ -162,7 +167,8 @@ def get_icrs_coordinates(name, parse=False, cache=False): for url in urls: try: resp_data = get_file_contents( - download_file(url, cache=cache, show_progress=False)) + download_file(url, cache=cache, show_progress=False) + ) break except urllib.error.URLError as e: exceptions.append(e) @@ -171,18 +177,20 @@ def get_icrs_coordinates(name, parse=False, cache=False): # There are some cases where urllib2 does not catch socket.timeout # especially while receiving response data on an already previously # working request - e.reason = ("Request took longer than the allowed " - f"{data.conf.remote_timeout:.1f} seconds") + e.reason = ( + "Request took longer than the allowed " + f"{data.conf.remote_timeout:.1f} seconds" + ) exceptions.append(e) continue # All Sesame URL's failed... else: - messages = [f"{url}: {e.reason}" - for url, e in zip(urls, exceptions)] - raise NameResolveError("All Sesame queries failed. Unable to " - "retrieve coordinates. See errors per URL " - f"below: \n {os.linesep.join(messages)}") + messages = [f"{url}: {e.reason}" for url, e in zip(urls, exceptions)] + raise NameResolveError( + "All Sesame queries failed. Unable to retrieve coordinates. See errors per" + f" URL below: \n {os.linesep.join(messages)}" + ) ra, dec = _parse_response(resp_data) @@ -190,10 +198,13 @@ def get_icrs_coordinates(name, parse=False, cache=False): if db == "A": err = f"Unable to find coordinates for name '{name}' using {url}" else: - err = f"Unable to find coordinates for name '{name}' in database {database} using {url}" + err = ( + f"Unable to find coordinates for name '{name}' in database" + f" {database} using {url}" + ) raise NameResolveError(err) # Return SkyCoord object - sc = SkyCoord(ra=ra, dec=dec, unit=(u.degree, u.degree), frame='icrs') + sc = SkyCoord(ra=ra, dec=dec, unit=(u.degree, u.degree), frame="icrs") return sc diff --git a/astropy/coordinates/orbital_elements.py b/astropy/coordinates/orbital_elements.py index 43ddb7bfe11..225edae5f14 100644 --- a/astropy/coordinates/orbital_elements.py +++ b/astropy/coordinates/orbital_elements.py @@ -79,7 +79,7 @@ (0, 2, 1, 0, -323, 1165), (1, 1, -1, 0, 299, 0), (2, 0, 3, 0, 294, 0), - (2, 0, -1, -2, 0, 8752) + (2, 0, -1, -2, 0, 8752), ) # Meeus 1998: table 47.B @@ -145,7 +145,7 @@ (4, 0, 1, -1, 132), (1, 0, -1, -1, -119), (4, -1, 0, -1, 115), - (2, -2, 0, 1, 107) + (2, -2, 0, 1, 107), ) """ @@ -157,29 +157,27 @@ Mc : Moon's mean anomaly F : Moon's argument of latitude (mean distance of Moon from its ascending node). """ -_coLc = (2.18316448e+02, 4.81267881e+05, -1.57860000e-03, - 1.85583502e-06, -1.53388349e-08) -_coD = (2.97850192e+02, 4.45267111e+05, -1.88190000e-03, - 1.83194472e-06, -8.84447000e-09) -_coM = (3.57529109e+02, 3.59990503e+04, -1.53600000e-04, - 4.08329931e-08) -_coMc = (1.34963396e+02, 4.77198868e+05, 8.74140000e-03, - 1.43474081e-05, -6.79717238e-08) -_coF = (9.32720950e+01, 4.83202018e+05, -3.65390000e-03, - -2.83607487e-07, 1.15833246e-09) +_coLc = (2.18316448e02, 4.81267881e05, -1.57860000e-03, 1.85583502e-06, -1.53388349e-08) +_coD = (2.97850192e02, 4.45267111e05, -1.88190000e-03, 1.83194472e-06, -8.84447000e-09) +_coM = (3.57529109e02, 3.59990503e04, -1.53600000e-04, 4.08329931e-08) +_coMc = (1.34963396e02, 4.77198868e05, 8.74140000e-03, 1.43474081e-05, -6.79717238e-08) +_coF = (9.32720950e01, 4.83202018e05, -3.65390000e-03, -2.83607487e-07, 1.15833246e-09) _coA1 = (119.75, 131.849) _coA2 = (53.09, 479264.290) _coA3 = (313.45, 481266.484) _coE = (1.0, -0.002516, -0.0000074) -@deprecated(since="5.0", - alternative="astropy.coordinates.get_moon", - message=("The private calc_moon function has been deprecated, " - "as its functionality is now available in ERFA. " - "Note that the coordinate system was not interpreted " - "quite correctly, leading to small inaccuracies. Please " - "use the public get_moon or get_body functions instead.")) +@deprecated( + since="5.0", + alternative="astropy.coordinates.get_moon", + message=( + "The private calc_moon function has been deprecated, as its functionality is" + " now available in ERFA. Note that the coordinate system was not interpreted" + " quite correctly, leading to small inaccuracies. Please use the public" + " get_moon or get_body functions instead." + ), +) def calc_moon(t): """ Lunar position model ELP2000-82 of (Chapront-Touze' and Chapront, 1983, 124, 50) @@ -204,7 +202,7 @@ def calc_moon(t): # number of centuries since J2000.0. # This should strictly speaking be in Ephemeris Time, but TDB or TT # will introduce error smaller than intrinsic accuracy of algorithm. - T = (t.tdb.jyear-2000.0)/100. + T = (t.tdb.jyear - 2000.0) / 100.0 # constants that are needed for all calculations Lc = u.Quantity(polyval(T, _coLc), u.deg) @@ -221,34 +219,39 @@ def calc_moon(t): suml = sumr = 0.0 for DNum, MNum, McNum, FNum, LFac, RFac in _MOON_L_R: corr = E ** abs(MNum) - suml += LFac*corr*np.sin(D*DNum+M*MNum+Mc*McNum+F*FNum) - sumr += RFac*corr*np.cos(D*DNum+M*MNum+Mc*McNum+F*FNum) + suml += LFac * corr * np.sin(D * DNum + M * MNum + Mc * McNum + F * FNum) + sumr += RFac * corr * np.cos(D * DNum + M * MNum + Mc * McNum + F * FNum) sumb = 0.0 for DNum, MNum, McNum, FNum, BFac in _MOON_B: corr = E ** abs(MNum) - sumb += BFac*corr*np.sin(D*DNum+M*MNum+Mc*McNum+F*FNum) + sumb += BFac * corr * np.sin(D * DNum + M * MNum + Mc * McNum + F * FNum) - suml += (3958*np.sin(A1) + 1962*np.sin(Lc-F) + 318*np.sin(A2)) - sumb += (-2235*np.sin(Lc) + 382*np.sin(A3) + 175*np.sin(A1-F) + - 175*np.sin(A1+F) + 127*np.sin(Lc-Mc) - 115*np.sin(Lc+Mc)) + suml += 3958 * np.sin(A1) + 1962 * np.sin(Lc - F) + 318 * np.sin(A2) + sumb += ( + -2235 * np.sin(Lc) + + 382 * np.sin(A3) + + 175 * np.sin(A1 - F) + + 175 * np.sin(A1 + F) + + 127 * np.sin(Lc - Mc) + - 115 * np.sin(Lc + Mc) + ) # ensure units - suml = suml*u.microdegree - sumb = sumb*u.microdegree + suml = suml * u.microdegree + sumb = sumb * u.microdegree # nutation of longitude - jd1, jd2 = get_jd12(t, 'tt') + jd1, jd2 = get_jd12(t, "tt") nut, _ = erfa.nut06a(jd1, jd2) - nut = nut*u.rad + nut = nut * u.rad # calculate ecliptic coordinates lon = Lc + suml + nut lat = sumb - dist = (385000.56+sumr/1000)*u.km + dist = (385000.56 + sumr / 1000) * u.km # Meeus algorithm gives GeocentricTrueEcliptic coordinates - ecliptic_coo = GeocentricTrueEcliptic(lon, lat, distance=dist, - obstime=t, equinox=t) + ecliptic_coo = GeocentricTrueEcliptic(lon, lat, distance=dist, obstime=t, equinox=t) return SkyCoord(ecliptic_coo.transform_to(ICRS())) diff --git a/astropy/coordinates/representation.py b/astropy/coordinates/representation.py index d6b984fb6ed..c2d66640a42 100644 --- a/astropy/coordinates/representation.py +++ b/astropy/coordinates/representation.py @@ -22,16 +22,27 @@ from .distances import Distance from .matrix_utilities import is_O3 -__all__ = ["BaseRepresentationOrDifferential", "BaseRepresentation", - "CartesianRepresentation", "SphericalRepresentation", - "UnitSphericalRepresentation", "RadialRepresentation", - "PhysicsSphericalRepresentation", "CylindricalRepresentation", - "BaseDifferential", "CartesianDifferential", - "BaseSphericalDifferential", "BaseSphericalCosLatDifferential", - "SphericalDifferential", "SphericalCosLatDifferential", - "UnitSphericalDifferential", "UnitSphericalCosLatDifferential", - "RadialDifferential", "CylindricalDifferential", - "PhysicsSphericalDifferential"] +__all__ = [ + "BaseRepresentationOrDifferential", + "BaseRepresentation", + "CartesianRepresentation", + "SphericalRepresentation", + "UnitSphericalRepresentation", + "RadialRepresentation", + "PhysicsSphericalRepresentation", + "CylindricalRepresentation", + "BaseDifferential", + "CartesianDifferential", + "BaseSphericalDifferential", + "BaseSphericalCosLatDifferential", + "SphericalDifferential", + "SphericalCosLatDifferential", + "UnitSphericalDifferential", + "UnitSphericalCosLatDifferential", + "RadialDifferential", + "CylindricalDifferential", + "PhysicsSphericalDifferential", +] # Module-level dict mapping representation string alias names to classes. # This is populated by __init_subclass__ when called by Representation or @@ -46,8 +57,8 @@ def _fqn_class(cls): - ''' Get the fully qualified name of a class ''' - return cls.__module__ + '.' + cls.__qualname__ + """Get the fully qualified name of a class""" + return cls.__module__ + "." + cls.__qualname__ def get_reprdiff_cls_hash(): @@ -58,8 +69,9 @@ def get_reprdiff_cls_hash(): """ global _REPRDIFF_HASH if _REPRDIFF_HASH is None: - _REPRDIFF_HASH = (hash(tuple(REPRESENTATION_CLASSES.items())) + - hash(tuple(DIFFERENTIAL_CLASSES.items()))) + _REPRDIFF_HASH = hash(tuple(REPRESENTATION_CLASSES.items())) + hash( + tuple(DIFFERENTIAL_CLASSES.items()) + ) return _REPRDIFF_HASH @@ -68,10 +80,10 @@ def _invalidate_reprdiff_cls_hash(): _REPRDIFF_HASH = None -def _array2string(values, prefix=''): +def _array2string(values, prefix=""): # Work around version differences for array2string. - kwargs = {'separator': ', ', 'prefix': prefix} - kwargs['formatter'] = {} + kwargs = {"separator": ", ", "prefix": prefix} + kwargs["formatter"] = {} return np.array2string(values, **kwargs) @@ -82,7 +94,8 @@ class BaseRepresentationOrDifferentialInfo(MixinInfo): required when the object is used as a mixin column within a table, but can be used as a general way to store meta information. """ - attrs_from_parent = {'unit'} # Indicates unit is read-only + + attrs_from_parent = {"unit"} # Indicates unit is read-only _supports_indexing = False @staticmethod @@ -90,9 +103,10 @@ def default_format(val): # Create numpy dtype so that numpy formatting will work. components = val.components values = tuple(getattr(val, component).value for component in components) - a = np.empty(getattr(val, 'shape', ()), - [(component, value.dtype) for component, value - in zip(components, values)]) + a = np.empty( + getattr(val, "shape", ()), + [(component, value.dtype) for component, value in zip(components, values)], + ) for component, value in zip(components, values): a[component] = value return str(a) @@ -107,9 +121,9 @@ def unit(self): return None unit = self._parent._unitstr - return unit[1:-1] if unit.startswith('(') else unit + return unit[1:-1] if unit.startswith("(") else unit - def new_like(self, reps, length, metadata_conflicts='warn', name=None): + def new_like(self, reps, length, metadata_conflicts="warn", name=None): """ Return a new instance like ``reps`` with ``length`` rows. @@ -135,8 +149,9 @@ def new_like(self, reps, length, metadata_conflicts='warn', name=None): """ # Get merged info attributes like shape, dtype, format, description, etc. - attrs = self.merge_cols_attributes(reps, metadata_conflicts, name, - ('meta', 'description')) + attrs = self.merge_cols_attributes( + reps, metadata_conflicts, name, ("meta", "description") + ) # Make a new representation or differential with the desired length # using the _apply / __getitem__ machinery to effectively return # rep0[[0, 0, ..., 0, 0]]. This will have the right shape, and @@ -150,10 +165,10 @@ def new_like(self, reps, length, metadata_conflicts='warn', name=None): try: out[0] = rep[0] except Exception as err: - raise ValueError('input representations are inconsistent.') from err + raise ValueError("input representations are inconsistent.") from err # Set (merged) info attributes. - for attr in ('name', 'meta', 'description'): + for attr in ("name", "meta", "description"): if attr in attrs: setattr(out.info, attr, attrs[attr]) @@ -183,18 +198,22 @@ def __init__(self, *args, **kwargs): # make argument a list, so we can pop them off. args = list(args) components = self.components - if (args and isinstance(args[0], self.__class__) - and all(arg is None for arg in args[1:])): + if ( + args + and isinstance(args[0], self.__class__) + and all(arg is None for arg in args[1:]) + ): rep_or_diff = args[0] - copy = kwargs.pop('copy', True) - attrs = [getattr(rep_or_diff, component) - for component in components] - if 'info' in rep_or_diff.__dict__: + copy = kwargs.pop("copy", True) + attrs = [getattr(rep_or_diff, component) for component in components] + if "info" in rep_or_diff.__dict__: self.info = rep_or_diff.info if kwargs: - raise TypeError(f'unexpected keyword arguments for case ' - f'where class instance is passed in: {kwargs}') + raise TypeError( + "unexpected keyword arguments for case " + f"where class instance is passed in: {kwargs}" + ) else: attrs = [] @@ -202,40 +221,46 @@ def __init__(self, *args, **kwargs): try: attr = args.pop(0) if args else kwargs.pop(component) except KeyError: - raise TypeError(f'__init__() missing 1 required positional ' - f'argument: {component!r}') from None + raise TypeError( + "__init__() missing 1 required positional " + f"argument: {component!r}" + ) from None if attr is None: - raise TypeError(f'__init__() missing 1 required positional ' - f'argument: {component!r} (or first ' - f'argument should be an instance of ' - f'{self.__class__.__name__}).') + raise TypeError( + "__init__() missing 1 required positional argument:" + f" {component!r} (or first argument should be an instance of" + f" {self.__class__.__name__})." + ) attrs.append(attr) - copy = args.pop(0) if args else kwargs.pop('copy', True) + copy = args.pop(0) if args else kwargs.pop("copy", True) if args: - raise TypeError(f'unexpected arguments: {args}') + raise TypeError(f"unexpected arguments: {args}") if kwargs: for component in components: if component in kwargs: - raise TypeError(f"__init__() got multiple values for " - f"argument {component!r}") + raise TypeError( + f"__init__() got multiple values for argument {component!r}" + ) - raise TypeError(f'unexpected keyword arguments: {kwargs}') + raise TypeError(f"unexpected keyword arguments: {kwargs}") # Pass attributes through the required initializing classes. - attrs = [self.attr_classes[component](attr, copy=copy, subok=True) - for component, attr in zip(components, attrs)] + attrs = [ + self.attr_classes[component](attr, copy=copy, subok=True) + for component, attr in zip(components, attrs) + ] try: bc_attrs = np.broadcast_arrays(*attrs, subok=True) except ValueError as err: if len(components) <= 2: - c_str = ' and '.join(components) + c_str = " and ".join(components) else: - c_str = ', '.join(components[:2]) + ', and ' + components[2] + c_str = ", ".join(components[:2]) + ", and " + components[2] raise ValueError(f"Input parameters {c_str} cannot be broadcast") from err # The output of np.broadcast_arrays() has limitations on writeability, so we perform @@ -250,13 +275,17 @@ def __init__(self, *args, **kwargs): # limited. # If the shape has not changed for a given component, we can proceed with using the # non-broadcasted array, which avoids writeability issues from np.broadcast_arrays(). - attrs = [(bc_attr.copy() if copy else bc_attr) if bc_attr.shape != attr.shape else attr - for attr, bc_attr in zip(attrs, bc_attrs)] + attrs = [ + (bc_attr.copy() if copy else bc_attr) + if bc_attr.shape != attr.shape + else attr + for attr, bc_attr in zip(attrs, bc_attrs) + ] # Set private attributes for the attributes. (If not defined explicitly # on the class, the metaclass will define properties to access these.) for component, attr in zip(components, attrs): - setattr(self, '_' + component, attr) + setattr(self, "_" + component, attr) @classmethod def get_name(cls): @@ -269,9 +298,9 @@ def get_name(cls): """ name = cls.__name__.lower() - if name.endswith('representation'): + if name.endswith("representation"): name = name[:-14] - elif name.endswith('differential'): + elif name.endswith("differential"): name = name[:-12] return name @@ -335,18 +364,19 @@ def __eq__(self, value): classes are identical and that the representation data are exactly equal. """ if self.__class__ is not value.__class__: - raise TypeError(f'cannot compare: objects must have same class: ' - f'{self.__class__.__name__} vs. ' - f'{value.__class__.__name__}') + raise TypeError( + "cannot compare: objects must have same class: " + f"{self.__class__.__name__} vs. {value.__class__.__name__}" + ) try: np.broadcast(self, value) except ValueError as exc: - raise ValueError(f'cannot compare: {exc}') from exc + raise ValueError(f"cannot compare: {exc}") from exc out = True for comp in self.components: - out &= (getattr(self, '_' + comp) == getattr(value, '_' + comp)) + out &= getattr(self, "_" + comp) == getattr(value, "_" + comp) return out @@ -385,25 +415,25 @@ def _apply(self, method, *args, **kwargs): new = super().__new__(self.__class__) for component in self.components: - setattr(new, '_' + component, - apply_method(getattr(self, component))) + setattr(new, "_" + component, apply_method(getattr(self, component))) # Copy other 'info' attr only if it has actually been defined. # See PR #3898 for further explanation and justification, along # with Quantity.__array_finalize__ - if 'info' in self.__dict__: + if "info" in self.__dict__: new.info = self.info return new def __setitem__(self, item, value): if value.__class__ is not self.__class__: - raise TypeError(f'can only set from object of same class: ' - f'{self.__class__.__name__} vs. ' - f'{value.__class__.__name__}') + raise TypeError( + "can only set from object of same class: " + f"{self.__class__.__name__} vs. {value.__class__.__name__}" + ) for component in self.components: - getattr(self, '_' + component)[item] = getattr(value, '_' + component) + getattr(self, "_" + component)[item] = getattr(value, "_" + component) @property def shape(self): @@ -509,27 +539,31 @@ def _unitstr(self): if len(units_set) == 1: unitstr = units_set.pop().to_string() else: - unitstr = '({})'.format( - ', '.join([self._units[component].to_string() - for component in self.components])) + unitstr = "({})".format( + ", ".join( + self._units[component].to_string() for component in self.components + ) + ) return unitstr def __str__(self): - return f'{_array2string(self._values)} {self._unitstr:s}' + return f"{_array2string(self._values)} {self._unitstr:s}" def __repr__(self): - prefixstr = ' ' + prefixstr = " " arrstr = _array2string(self._values, prefix=prefixstr) - diffstr = '' - if getattr(self, 'differentials', None): - diffstr = '\n (has differentials w.r.t.: {})'.format( - ', '.join([repr(key) for key in self.differentials.keys()])) + diffstr = "" + if getattr(self, "differentials", None): + diffstr = "\n (has differentials w.r.t.: {})".format( + ", ".join([repr(key) for key in self.differentials.keys()]) + ) - unitstr = ('in ' + self._unitstr) if self._unitstr else '[dimensionless]' - return '<{} ({}) {:s}\n{}{}{}>'.format( - self.__class__.__name__, ', '.join(self.components), - unitstr, prefixstr, arrstr, diffstr) + unitstr = ("in " + self._unitstr) if self._unitstr else "[dimensionless]" + return ( + f"<{self.__class__.__name__} ({', '.join(self.components)})" + f" {unitstr:s}\n{prefixstr}{arrstr}{diffstr}>" + ) def _make_getter(component): @@ -543,34 +577,34 @@ def _make_getter(component): """ # This has to be done in a function to ensure the reference to component # is not lost/redirected. - component = '_' + component + component = "_" + component def get_component(self): return getattr(self, component) + return get_component class RepresentationInfo(BaseRepresentationOrDifferentialInfo): - @property def _represent_as_dict_attrs(self): attrs = super()._represent_as_dict_attrs if self._parent._differentials: - attrs += ('differentials',) + attrs += ("differentials",) return attrs def _represent_as_dict(self, attrs=None): out = super()._represent_as_dict(attrs) - for key, value in out.pop('differentials', {}).items(): - out[f'differentials.{key}'] = value + for key, value in out.pop("differentials", {}).items(): + out[f"differentials.{key}"] = value return out def _construct_from_dict(self, map): differentials = {} for key in list(map.keys()): - if key.startswith('differentials.'): + if key.startswith("differentials."): differentials[key[14:]] = map.pop(key) - map['differentials'] = differentials + map["differentials"] = differentials return super()._construct_from_dict(map) @@ -611,12 +645,13 @@ class BaseRepresentation(BaseRepresentationOrDifferential): def __init_subclass__(cls, **kwargs): # Register representation name (except for BaseRepresentation) - if cls.__name__ == 'BaseRepresentation': + if cls.__name__ == "BaseRepresentation": return - if not hasattr(cls, 'attr_classes'): - raise NotImplementedError('Representations must have an ' - '"attr_classes" class attribute.') + if not hasattr(cls, "attr_classes"): + raise NotImplementedError( + 'Representations must have an "attr_classes" class attribute.' + ) repr_name = cls.get_name() # first time a duplicate is added @@ -632,8 +667,9 @@ def __init_subclass__(cls, **kwargs): raise ValueError(f'Representation "{fqn_cls}" already defined') msg = ( - f'Representation "{repr_name}" already defined, removing it to avoid confusion.' - f'Use qualnames "{fqn_cls}" and "{fqn_existing}" or class instances directly' + f'Representation "{repr_name}" already defined, removing it to avoid' + f' confusion.Use qualnames "{fqn_cls}" and "{fqn_existing}" or class' + " instances directly" ) warnings.warn(msg, DuplicateRepresentationWarning) @@ -644,13 +680,13 @@ def __init_subclass__(cls, **kwargs): # further definitions with the same name, just add qualname elif repr_name in DUPLICATE_REPRESENTATIONS: fqn_cls = _fqn_class(cls) - warnings.warn(f'Representation "{repr_name}" already defined, using qualname ' - f'"{fqn_cls}".') + warnings.warn( + f'Representation "{repr_name}" already defined, using qualname ' + f'"{fqn_cls}".' + ) repr_name = fqn_cls if repr_name in REPRESENTATION_CLASSES: - raise ValueError( - f'Representation "{repr_name}" already defined' - ) + raise ValueError(f'Representation "{repr_name}" already defined') REPRESENTATION_CLASSES[repr_name] = cls _invalidate_reprdiff_cls_hash() @@ -658,17 +694,21 @@ def __init_subclass__(cls, **kwargs): # define getters for any component that does not yet have one. for component in cls.attr_classes: if not hasattr(cls, component): - setattr(cls, component, - property(_make_getter(component), - doc=f"The '{component}' component of the points(s).")) + setattr( + cls, + component, + property( + _make_getter(component), + doc=f"The '{component}' component of the points(s).", + ), + ) super().__init_subclass__(**kwargs) def __init__(self, *args, differentials=None, **kwargs): # Handle any differentials passed in. super().__init__(*args, **kwargs) - if (differentials is None - and args and isinstance(args[0], self.__class__)): + if differentials is None and args and isinstance(args[0], self.__class__): differentials = args[0]._differentials self._differentials = self._validate_differentials(differentials) @@ -687,11 +727,13 @@ def _validate_differentials(self, differentials): elif isinstance(differentials, BaseDifferential): # We can't handle auto-determining the key for this combo - if (isinstance(differentials, RadialDifferential) and - isinstance(self, UnitSphericalRepresentation)): - raise ValueError("To attach a RadialDifferential to a " - "UnitSphericalRepresentation, you must supply " - "a dictionary with an appropriate key.") + if isinstance(differentials, RadialDifferential) and isinstance( + self, UnitSphericalRepresentation + ): + raise ValueError( + "To attach a RadialDifferential to a UnitSphericalRepresentation," + " you must supply a dictionary with an appropriate key." + ) key = differentials._get_deriv_key(self) differentials = {key: differentials} @@ -700,13 +742,15 @@ def _validate_differentials(self, differentials): try: diff = differentials[key] except TypeError as err: - raise TypeError("'differentials' argument must be a " - "dictionary-like object") from err + raise TypeError( + "'differentials' argument must be a dictionary-like object" + ) from err diff._check_base(self) - if (isinstance(diff, RadialDifferential) and - isinstance(self, UnitSphericalRepresentation)): + if isinstance(diff, RadialDifferential) and isinstance( + self, UnitSphericalRepresentation + ): # We trust the passing of a key for a RadialDifferential # attached to a UnitSphericalRepresentation because it will not # have a paired component name (UnitSphericalRepresentation has @@ -716,9 +760,10 @@ def _validate_differentials(self, differentials): else: expected_key = diff._get_deriv_key(self) if key != expected_key: - raise ValueError("For differential object '{}', expected " - "unit key = '{}' but received key = '{}'" - .format(repr(diff), expected_key, key)) + raise ValueError( + f"For differential object '{repr(diff)}', expected " + f"unit key = '{expected_key}' but received key = '{key}'" + ) # For now, we are very rigid: differentials must have the same shape # as the representation. This makes it easier to handle __getitem__ @@ -727,9 +772,10 @@ def _validate_differentials(self, differentials): if diff.shape != self.shape: # TODO: message of IncompatibleShapeError is not customizable, # so use a valueerror instead? - raise ValueError("Shape of differentials must be the same " - "as the shape of the representation ({} vs " - "{})".format(diff.shape, self.shape)) + raise ValueError( + "Shape of differentials must be the same " + f"as the shape of the representation ({diff.shape} vs {self.shape})" + ) return differentials @@ -739,9 +785,10 @@ def _raise_if_has_differentials(self, op_name): supported when a representation has differentials attached. """ if self.differentials: - raise TypeError("Operation '{}' is not supported when " - "differentials are attached to a {}." - .format(op_name, self.__class__.__name__)) + raise TypeError( + f"Operation '{op_name}' is not supported when " + f"differentials are attached to a {self.__class__.__name__}." + ) @classproperty def _compatible_differentials(cls): @@ -800,38 +847,38 @@ def _re_represent_differentials(self, new_rep, differential_class): return dict() if not self.differentials and differential_class: - raise ValueError("No differentials associated with this " - "representation!") + raise ValueError("No differentials associated with this representation!") - elif (len(self.differentials) == 1 and - inspect.isclass(differential_class) and - issubclass(differential_class, BaseDifferential)): + elif ( + len(self.differentials) == 1 + and inspect.isclass(differential_class) + and issubclass(differential_class, BaseDifferential) + ): # TODO: is there a better way to do this? differential_class = { list(self.differentials.keys())[0]: differential_class } elif differential_class.keys() != self.differentials.keys(): - raise ValueError("Desired differential classes must be passed in " - "as a dictionary with keys equal to a string " - "representation of the unit of the derivative " - "for each differential stored with this " - f"representation object ({self.differentials})") + raise ValueError( + "Desired differential classes must be passed in as a dictionary with" + " keys equal to a string representation of the unit of the derivative" + " for each differential stored with this " + f"representation object ({self.differentials})" + ) new_diffs = dict() for k in self.differentials: diff = self.differentials[k] try: - new_diffs[k] = diff.represent_as(differential_class[k], - base=self) + new_diffs[k] = diff.represent_as(differential_class[k], base=self) except Exception as err: - if (differential_class[k] not in - new_rep._compatible_differentials): - raise TypeError("Desired differential class {} is not " - "compatible with the desired " - "representation class {}" - .format(differential_class[k], - new_rep.__class__)) from err + if differential_class[k] not in new_rep._compatible_differentials: + raise TypeError( + f"Desired differential class {differential_class[k]} is not " + "compatible with the desired " + f"representation class {new_rep.__class__}" + ) from err else: raise @@ -862,9 +909,10 @@ def represent_as(self, other_class, differential_class=None): else: if isinstance(other_class, str): - raise ValueError("Input to a representation's represent_as " - "must be a class, not a string. For " - "strings, use frame objects") + raise ValueError( + "Input to a representation's represent_as must be a class, not " + "a string. For strings, use frame objects." + ) if other_class is not self.__class__: # The default is to convert via cartesian coordinates @@ -873,7 +921,8 @@ def represent_as(self, other_class, differential_class=None): new_rep = self new_rep._differentials = self._re_represent_differentials( - new_rep, differential_class) + new_rep, differential_class + ) return new_rep @@ -892,9 +941,9 @@ def transform(self, matrix): """ # route transformation through Cartesian difs_cls = {k: CartesianDifferential for k in self.differentials.keys()} - crep = self.represent_as(CartesianRepresentation, - differential_class=difs_cls - ).transform(matrix) + crep = self.represent_as( + CartesianRepresentation, differential_class=difs_cls + ).transform(matrix) # move back to original representation difs_cls = {k: diff.__class__ for k, diff in self.differentials.items()} @@ -927,10 +976,10 @@ def with_differentials(self, differentials): # We shallow copy the differentials dictionary so we don't update the # current object's dictionary when adding new keys - new_rep = self.__class__(*args, differentials=self.differentials.copy(), - copy=False) - new_rep._differentials.update( - new_rep._validate_differentials(differentials)) + new_rep = self.__class__( + *args, differentials=self.differentials.copy(), copy=False + ) + new_rep._differentials.update(new_rep._validate_differentials(differentials)) return new_rep @@ -974,11 +1023,12 @@ def __eq__(self, value): # super() checks that the class is identical so can this even happen? # (same class, different differentials ?) if self._differentials.keys() != value._differentials.keys(): - raise ValueError('cannot compare: objects must have same differentials') + raise ValueError("cannot compare: objects must have same differentials") - for self_diff, value_diff in zip(self._differentials.values(), - value._differentials.values()): - out &= (self_diff == value_diff) + for self_diff, value_diff in zip( + self._differentials.values(), value._differentials.values() + ): + out &= self_diff == value_diff return out @@ -1008,35 +1058,46 @@ def _apply(self, method, *args, **kwargs): """ rep = super()._apply(method, *args, **kwargs) - rep._differentials = {k: diff._apply(method, *args, **kwargs) - for k, diff in self._differentials.items()} + rep._differentials = { + k: diff._apply(method, *args, **kwargs) + for k, diff in self._differentials.items() + } return rep def __setitem__(self, item, value): if not isinstance(value, BaseRepresentation): - raise TypeError(f'value must be a representation instance, ' - f'not {type(value)}.') + raise TypeError( + f"value must be a representation instance, not {type(value)}." + ) - if not (isinstance(value, self.__class__) - or len(value.attr_classes) == len(self.attr_classes)): + if not ( + isinstance(value, self.__class__) + or len(value.attr_classes) == len(self.attr_classes) + ): raise ValueError( - f'value must be representable as {self.__class__.__name__} ' - f'without loss of information.') + f"value must be representable as {self.__class__.__name__} " + "without loss of information." + ) diff_classes = {} if self._differentials: if self._differentials.keys() != value._differentials.keys(): - raise ValueError('value must have the same differentials.') + raise ValueError("value must have the same differentials.") for key, self_diff in self._differentials.items(): diff_classes[key] = self_diff_cls = self_diff.__class__ value_diff_cls = value._differentials[key].__class__ - if not (isinstance(value_diff_cls, self_diff_cls) - or (len(value_diff_cls.attr_classes) - == len(self_diff_cls.attr_classes))): + if not ( + isinstance(value_diff_cls, self_diff_cls) + or ( + len(value_diff_cls.attr_classes) + == len(self_diff_cls.attr_classes) + ) + ): raise ValueError( - f'value differential {key!r} must be representable as ' - f'{self_diff.__class__.__name__} without loss of information.') + f"value differential {key!r} must be representable as " + f"{self_diff.__class__.__name__} without loss of information." + ) value = value.represent_as(self.__class__, diff_classes) super().__setitem__(item, value) @@ -1132,10 +1193,13 @@ def norm(self): norm : `astropy.units.Quantity` Vector norm, with the same shape as the representation. """ - return np.sqrt(functools.reduce( - operator.add, (getattr(self, component)**2 - for component, cls in self.attr_classes.items() - if not issubclass(cls, Angle)))) + return np.sqrt( + sum( + getattr(self, component) ** 2 + for component, cls in self.attr_classes.items() + if not issubclass(cls, Angle) + ) + ) def mean(self, *args, **kwargs): """Vector mean. @@ -1153,7 +1217,7 @@ def mean(self, *args, **kwargs): mean : `~astropy.coordinates.BaseRepresentation` subclass instance Vector mean, in the same representation as that of the input. """ - self._raise_if_has_differentials('mean') + self._raise_if_has_differentials("mean") return self.from_cartesian(self.to_cartesian().mean(*args, **kwargs)) def sum(self, *args, **kwargs): @@ -1172,7 +1236,7 @@ def sum(self, *args, **kwargs): sum : `~astropy.coordinates.BaseRepresentation` subclass instance Vector sum, in the same representation as that of the input. """ - self._raise_if_has_differentials('sum') + self._raise_if_has_differentials("sum") return self.from_cartesian(self.to_cartesian().sum(*args, **kwargs)) def dot(self, other): @@ -1215,7 +1279,7 @@ def cross(self, other): With vectors perpendicular to both ``self`` and ``other``, in the same type of representation as ``self``. """ - self._raise_if_has_differentials('cross') + self._raise_if_has_differentials("cross") return self.from_cartesian(self.to_cartesian().cross(other)) @@ -1252,17 +1316,15 @@ class CartesianRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'x': u.Quantity, - 'y': u.Quantity, - 'z': u.Quantity} + attr_classes = {"x": u.Quantity, "y": u.Quantity, "z": u.Quantity} _xyz = None - def __init__(self, x, y=None, z=None, unit=None, xyz_axis=None, - differentials=None, copy=True): - + def __init__( + self, x, y=None, z=None, unit=None, xyz_axis=None, differentials=None, copy=True + ): if y is None and z is None: - if isinstance(x, np.ndarray) and x.dtype.kind not in 'OV': + if isinstance(x, np.ndarray) and x.dtype.kind not in "OV": # Short-cut for 3-D array input. x = u.Quantity(x, unit, copy=copy, subok=True) # Keep a link to the array with all three coordinates @@ -1278,25 +1340,29 @@ def __init__(self, x, y=None, z=None, unit=None, xyz_axis=None, self._differentials = self._validate_differentials(differentials) return - elif (isinstance(x, CartesianRepresentation) - and unit is None and xyz_axis is None): + elif ( + isinstance(x, CartesianRepresentation) + and unit is None + and xyz_axis is None + ): if differentials is None: differentials = x._differentials - return super().__init__(x, differentials=differentials, - copy=copy) + return super().__init__(x, differentials=differentials, copy=copy) else: x, y, z = x if xyz_axis is not None: - raise ValueError("xyz_axis should only be set if x, y, and z are " - "in a single array passed in through x, " - "i.e., y and z should not be not given.") + raise ValueError( + "xyz_axis should only be set if x, y, and z are in a single array" + " passed in through x, i.e., y and z should not be not given." + ) if y is None or z is None: - raise ValueError("x, y, and z are required to instantiate {}" - .format(self.__class__.__name__)) + raise ValueError( + f"x, y, and z are required to instantiate {self.__class__.__name__}" + ) if unit is not None: x = u.Quantity(x, unit, copy=copy, subok=True) @@ -1305,21 +1371,24 @@ def __init__(self, x, y=None, z=None, unit=None, xyz_axis=None, copy = False super().__init__(x, y, z, copy=copy, differentials=differentials) - if not (self._x.unit.is_equivalent(self._y.unit) and - self._x.unit.is_equivalent(self._z.unit)): + if not ( + self._x.unit.is_equivalent(self._y.unit) + and self._x.unit.is_equivalent(self._z.unit) + ): raise u.UnitsError("x, y, and z should have matching physical types") def unit_vectors(self): - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - o = np.broadcast_to(0.*u.one, self.shape, subok=True) + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + o = np.broadcast_to(0.0 * u.one, self.shape, subok=True) return { - 'x': CartesianRepresentation(l, o, o, copy=False), - 'y': CartesianRepresentation(o, l, o, copy=False), - 'z': CartesianRepresentation(o, o, l, copy=False)} + "x": CartesianRepresentation(l, o, o, copy=False), + "y": CartesianRepresentation(o, l, o, copy=False), + "z": CartesianRepresentation(o, o, l, copy=False), + } def scale_factors(self): - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'x': l, 'y': l, 'z': l} + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"x": l, "y": l, "z": l} def get_xyz(self, xyz_axis=0): """Return a vector array of the x, y, and z coordinates. @@ -1397,8 +1466,9 @@ def transform(self, matrix): # transformed representation rep = self.__class__(p, xyz_axis=-1, copy=False) # Handle differentials attached to this representation - new_diffs = {k: d.transform(matrix, self, rep) - for k, d in self.differentials.items()} + new_diffs = { + k: d.transform(matrix, self, rep) for k, d in self.differentials.items() + } return rep.with_differentials(new_diffs) def _combine_operation(self, op, other, reverse=False): @@ -1409,11 +1479,13 @@ def _combine_operation(self, op, other, reverse=False): except Exception: return NotImplemented - first, second = ((self, other_c) if not reverse else - (other_c, self)) - return self.__class__(*(op(getattr(first, component), - getattr(second, component)) - for component in first.components)) + first, second = (self, other_c) if not reverse else (other_c, self) + return self.__class__( + *( + op(getattr(first, component), getattr(second, component)) + for component in first.components + ) + ) def norm(self): """Vector norm. @@ -1442,8 +1514,8 @@ def mean(self, *args, **kwargs): that ``axis`` is the entry in the ``shape`` of the representation, and that the ``out`` argument cannot be used. """ - self._raise_if_has_differentials('mean') - return self._apply('mean', *args, **kwargs) + self._raise_if_has_differentials("mean") + return self._apply("mean", *args, **kwargs) def sum(self, *args, **kwargs): """Vector sum. @@ -1455,8 +1527,8 @@ def sum(self, *args, **kwargs): that ``axis`` is the entry in the ``shape`` of the representation, and that the ``out`` argument cannot be used. """ - self._raise_if_has_differentials('sum') - return self._apply('sum', *args, **kwargs) + self._raise_if_has_differentials("sum") + return self._apply("sum", *args, **kwargs) def dot(self, other): """Dot product of two representations. @@ -1478,12 +1550,12 @@ def dot(self, other): try: other_c = other.to_cartesian() except Exception as err: - raise TypeError("cannot only take dot product with another " - "representation, not a {} instance." - .format(type(other))) from err + raise TypeError( + "can only take dot product with another " + f"representation, not a {type(other)} instance." + ) from err # erfa pdp: p-vector inner (=scalar=dot) product. - return erfa_ufunc.pdp(self.get_xyz(xyz_axis=-1), - other_c.get_xyz(xyz_axis=-1)) + return erfa_ufunc.pdp(self.get_xyz(xyz_axis=-1), other_c.get_xyz(xyz_axis=-1)) def cross(self, other): """Cross product of two representations. @@ -1498,16 +1570,16 @@ def cross(self, other): cross_product : `~astropy.coordinates.CartesianRepresentation` With vectors perpendicular to both ``self`` and ``other``. """ - self._raise_if_has_differentials('cross') + self._raise_if_has_differentials("cross") try: other_c = other.to_cartesian() except Exception as err: - raise TypeError("cannot only take cross product with another " - "representation, not a {} instance." - .format(type(other))) from err + raise TypeError( + "cannot only take cross product with another " + f"representation, not a {type(other)} instance." + ) from err # erfa pxp: p-vector outer (=vector=cross) product. - sxo = erfa_ufunc.pxp(self.get_xyz(xyz_axis=-1), - other_c.get_xyz(xyz_axis=-1)) + sxo = erfa_ufunc.pxp(self.get_xyz(xyz_axis=-1), other_c.get_xyz(xyz_axis=-1)) return self.__class__(sxo, xyz_axis=-1) @@ -1539,8 +1611,7 @@ class UnitSphericalRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'lon': Longitude, - 'lat': Latitude} + attr_classes = {"lon": Longitude, "lat": Latitude} @classproperty def _dimensional_representation(cls): @@ -1551,9 +1622,13 @@ def __init__(self, lon, lat=None, differentials=None, copy=True): @classproperty def _compatible_differentials(cls): - return [UnitSphericalDifferential, UnitSphericalCosLatDifferential, - SphericalDifferential, SphericalCosLatDifferential, - RadialDifferential] + return [ + UnitSphericalDifferential, + UnitSphericalCosLatDifferential, + SphericalDifferential, + SphericalCosLatDifferential, + RadialDifferential, + ] # Could let the metaclass define these automatically, but good to have # a bit clearer docstrings. @@ -1575,15 +1650,16 @@ def unit_vectors(self): sinlon, coslon = np.sin(self.lon), np.cos(self.lon) sinlat, coslat = np.sin(self.lat), np.cos(self.lat) return { - 'lon': CartesianRepresentation(-sinlon, coslon, 0., copy=False), - 'lat': CartesianRepresentation(-sinlat*coslon, -sinlat*sinlon, - coslat, copy=False)} + "lon": CartesianRepresentation(-sinlon, coslon, 0.0, copy=False), + "lat": CartesianRepresentation( + -sinlat * coslon, -sinlat * sinlon, coslat, copy=False + ), + } def scale_factors(self, omit_coslat=False): - sf_lat = np.broadcast_to(1./u.radian, self.shape, subok=True) + sf_lat = np.broadcast_to(1.0 / u.radian, self.shape, subok=True) sf_lon = sf_lat if omit_coslat else np.cos(self.lat) / u.radian - return {'lon': sf_lon, - 'lat': sf_lat} + return {"lon": sf_lon, "lat": sf_lat} def to_cartesian(self): """ @@ -1613,11 +1689,11 @@ def represent_as(self, other_class, differential_class=None): # in the other class (here "s / m"). For more info, see PR #11467 if inspect.isclass(other_class) and not differential_class: if issubclass(other_class, PhysicsSphericalRepresentation): - return other_class(phi=self.lon, theta=90 * u.deg - self.lat, - r=1.0, copy=False) + return other_class( + phi=self.lon, theta=90 * u.deg - self.lat, r=1.0, copy=False + ) elif issubclass(other_class, SphericalRepresentation): - return other_class(lon=self.lon, lat=self.lat, distance=1.0, - copy=False) + return other_class(lon=self.lon, lat=self.lat, distance=1.0, copy=False) return super().represent_as(other_class, differential_class) @@ -1651,33 +1727,38 @@ def transform(self, matrix): lon, lat = erfa_ufunc.c2s(p) rep = self.__class__(lon=lon, lat=lat) # handle differentials - new_diffs = {k: d.transform(matrix, self, rep) - for k, d in self.differentials.items()} + new_diffs = { + k: d.transform(matrix, self, rep) for k, d in self.differentials.items() + } rep = rep.with_differentials(new_diffs) else: # switch to dimensional representation rep = self._dimensional_representation( - lon=self.lon, lat=self.lat, distance=1, - differentials=self.differentials + lon=self.lon, lat=self.lat, distance=1, differentials=self.differentials ).transform(matrix) return rep def _scale_operation(self, op, *args): return self._dimensional_representation( - lon=self.lon, lat=self.lat, distance=1., - differentials=self.differentials)._scale_operation(op, *args) + lon=self.lon, lat=self.lat, distance=1.0, differentials=self.differentials + )._scale_operation(op, *args) def __neg__(self): - if any(differential.base_representation is not self.__class__ - for differential in self.differentials.values()): + if any( + differential.base_representation is not self.__class__ + for differential in self.differentials.values() + ): return super().__neg__() - result = self.__class__(self.lon + 180. * u.deg, -self.lat, copy=False) + result = self.__class__(self.lon + 180.0 * u.deg, -self.lat, copy=False) for key, differential in self.differentials.items(): - new_comps = (op(getattr(differential, comp)) - for op, comp in zip((operator.pos, operator.neg), - differential.components)) + new_comps = ( + op(getattr(differential, comp)) + for op, comp in zip( + (operator.pos, operator.neg), differential.components + ) + ) result.differentials[key] = differential.__class__(*new_comps, copy=False) return result @@ -1693,8 +1774,7 @@ def norm(self): norm : `~astropy.units.Quantity` ['dimensionless'] Dimensionless ones, with the same shape as the representation. """ - return u.Quantity(np.ones(self.shape), u.dimensionless_unscaled, - copy=False) + return u.Quantity(np.ones(self.shape), u.dimensionless_unscaled, copy=False) def _combine_operation(self, op, other, reverse=False): self._raise_if_has_differentials(op.__name__) @@ -1716,9 +1796,10 @@ def mean(self, *args, **kwargs): that ``axis`` is the entry in the ``shape`` of the representation, and that the ``out`` argument cannot be used. """ - self._raise_if_has_differentials('mean') + self._raise_if_has_differentials("mean") return self._dimensional_representation.from_cartesian( - self.to_cartesian().mean(*args, **kwargs)) + self.to_cartesian().mean(*args, **kwargs) + ) def sum(self, *args, **kwargs): """Vector sum. @@ -1731,9 +1812,10 @@ def sum(self, *args, **kwargs): that ``axis`` is the entry in the ``shape`` of the representation, and that the ``out`` argument cannot be used. """ - self._raise_if_has_differentials('sum') + self._raise_if_has_differentials("sum") return self._dimensional_representation.from_cartesian( - self.to_cartesian().sum(*args, **kwargs)) + self.to_cartesian().sum(*args, **kwargs) + ) def cross(self, other): """Cross product of two representations. @@ -1752,9 +1834,10 @@ def cross(self, other): cross_product : `~astropy.coordinates.SphericalRepresentation` With vectors perpendicular to both ``self`` and ``other``. """ - self._raise_if_has_differentials('cross') + self._raise_if_has_differentials("cross") return self._dimensional_representation.from_cartesian( - self.to_cartesian().cross(other)) + self.to_cartesian().cross(other) + ) class RadialRepresentation(BaseRepresentation): @@ -1784,7 +1867,7 @@ class RadialRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'distance': u.Quantity} + attr_classes = {"distance": u.Quantity} def __init__(self, distance, differentials=None, copy=True): super().__init__(distance, differentials=differentials, copy=copy) @@ -1798,17 +1881,19 @@ def distance(self): def unit_vectors(self): """Cartesian unit vectors are undefined for radial representation.""" - raise NotImplementedError('Cartesian unit vectors are undefined for ' - '{} instances'.format(self.__class__)) + raise NotImplementedError( + f"Cartesian unit vectors are undefined for {self.__class__} instances" + ) def scale_factors(self): - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'distance': l} + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"distance": l} def to_cartesian(self): """Cannot convert radial representation to cartesian.""" - raise NotImplementedError('cannot convert {} instance to cartesian.' - .format(self.__class__)) + raise NotImplementedError( + f"cannot convert {self.__class__} instance to cartesian." + ) @classmethod def from_cartesian(cls, cart): @@ -1859,8 +1944,10 @@ def transform(self, matrix): scl = matrix[..., 0, 0] # check that the matrix is a scaled identity matrix on the last 2 axes. if np.any(matrix != scl[..., np.newaxis, np.newaxis] * np.identity(3)): - raise ValueError("Radial representations can only be " - "transformed by a scaled identity matrix") + raise ValueError( + "Radial representations can only be " + "transformed by a scaled identity matrix" + ) return self * scl @@ -1868,7 +1955,7 @@ def transform(self, matrix): def _spherical_op_funcs(op, *args): """For given operator, return functions that adjust lon, lat, distance.""" if op is operator.neg: - return lambda x: x+180*u.deg, operator.neg, operator.pos + return lambda x: x + 180 * u.deg, operator.neg, operator.pos try: scale_sign = np.sign(args[0]) @@ -1877,9 +1964,11 @@ def _spherical_op_funcs(op, *args): return operator.pos, operator.pos, lambda x: op(x, *args) scale = abs(args[0]) - return (lambda x: x + 180*u.deg*np.signbit(scale_sign), - lambda x: x * scale_sign, - lambda x: op(x, scale)) + return ( + lambda x: x + 180 * u.deg * np.signbit(scale_sign), + lambda x: x * scale_sign, + lambda x: op(x, scale), + ) class SphericalRepresentation(BaseRepresentation): @@ -1915,33 +2004,36 @@ class SphericalRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'lon': Longitude, - 'lat': Latitude, - 'distance': u.Quantity} + attr_classes = {"lon": Longitude, "lat": Latitude, "distance": u.Quantity} _unit_representation = UnitSphericalRepresentation - def __init__(self, lon, lat=None, distance=None, differentials=None, - copy=True): - super().__init__(lon, lat, distance, copy=copy, - differentials=differentials) - if (not isinstance(self._distance, Distance) - and self._distance.unit.physical_type == 'length'): + def __init__(self, lon, lat=None, distance=None, differentials=None, copy=True): + super().__init__(lon, lat, distance, copy=copy, differentials=differentials) + if ( + not isinstance(self._distance, Distance) + and self._distance.unit.physical_type == "length" + ): try: self._distance = Distance(self._distance, copy=False) except ValueError as e: - if e.args[0].startswith('distance must be >= 0'): - raise ValueError("Distance must be >= 0. To allow negative " - "distance values, you must explicitly pass" - " in a `Distance` object with the the " - "argument 'allow_negative=True'.") from e + if e.args[0].startswith("distance must be >= 0"): + raise ValueError( + "Distance must be >= 0. To allow negative distance values, you" + " must explicitly pass in a `Distance` object with the the " + "argument 'allow_negative=True'." + ) from e else: raise @classproperty def _compatible_differentials(cls): - return [UnitSphericalDifferential, UnitSphericalCosLatDifferential, - SphericalDifferential, SphericalCosLatDifferential, - RadialDifferential] + return [ + UnitSphericalDifferential, + UnitSphericalCosLatDifferential, + SphericalDifferential, + SphericalCosLatDifferential, + RadialDifferential, + ] @property def lon(self): @@ -1968,36 +2060,44 @@ def unit_vectors(self): sinlon, coslon = np.sin(self.lon), np.cos(self.lon) sinlat, coslat = np.sin(self.lat), np.cos(self.lat) return { - 'lon': CartesianRepresentation(-sinlon, coslon, 0., copy=False), - 'lat': CartesianRepresentation(-sinlat*coslon, -sinlat*sinlon, - coslat, copy=False), - 'distance': CartesianRepresentation(coslat*coslon, coslat*sinlon, - sinlat, copy=False)} + "lon": CartesianRepresentation(-sinlon, coslon, 0.0, copy=False), + "lat": CartesianRepresentation( + -sinlat * coslon, -sinlat * sinlon, coslat, copy=False + ), + "distance": CartesianRepresentation( + coslat * coslon, coslat * sinlon, sinlat, copy=False + ), + } def scale_factors(self, omit_coslat=False): sf_lat = self.distance / u.radian sf_lon = sf_lat if omit_coslat else sf_lat * np.cos(self.lat) - sf_distance = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'lon': sf_lon, - 'lat': sf_lat, - 'distance': sf_distance} + sf_distance = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"lon": sf_lon, "lat": sf_lat, "distance": sf_distance} def represent_as(self, other_class, differential_class=None): # Take a short cut if the other class is a spherical representation if inspect.isclass(other_class): if issubclass(other_class, PhysicsSphericalRepresentation): - diffs = self._re_represent_differentials(other_class, - differential_class) - return other_class(phi=self.lon, theta=90 * u.deg - self.lat, - r=self.distance, differentials=diffs, - copy=False) + diffs = self._re_represent_differentials( + other_class, differential_class + ) + return other_class( + phi=self.lon, + theta=90 * u.deg - self.lat, + r=self.distance, + differentials=diffs, + copy=False, + ) elif issubclass(other_class, UnitSphericalRepresentation): - diffs = self._re_represent_differentials(other_class, - differential_class) - return other_class(lon=self.lon, lat=self.lat, - differentials=diffs, copy=False) + diffs = self._re_represent_differentials( + other_class, differential_class + ) + return other_class( + lon=self.lon, lat=self.lat, differentials=diffs, copy=False + ) return super().represent_as(other_class, differential_class) @@ -2047,8 +2147,9 @@ def transform(self, matrix): rep = self.__class__(lon=lon, lat=lat, distance=self.distance * ur) # handle differentials - new_diffs = {k: d.transform(matrix, self, rep) - for k, d in self.differentials.items()} + new_diffs = { + k: d.transform(matrix, self, rep) for k, d in self.differentials.items() + } return rep.with_differentials(new_diffs) def norm(self): @@ -2067,18 +2168,24 @@ def norm(self): def _scale_operation(self, op, *args): # TODO: expand special-casing to UnitSpherical and RadialDifferential. - if any(differential.base_representation is not self.__class__ - for differential in self.differentials.values()): + if any( + differential.base_representation is not self.__class__ + for differential in self.differentials.values() + ): return super()._scale_operation(op, *args) lon_op, lat_op, distance_op = _spherical_op_funcs(op, *args) - result = self.__class__(lon_op(self.lon), lat_op(self.lat), - distance_op(self.distance), copy=False) + result = self.__class__( + lon_op(self.lon), lat_op(self.lat), distance_op(self.distance), copy=False + ) for key, differential in self.differentials.items(): - new_comps = (op(getattr(differential, comp)) for op, comp in zip( - (operator.pos, lat_op, distance_op), - differential.components)) + new_comps = ( + op(getattr(differential, comp)) + for op, comp in zip( + (operator.pos, lat_op, distance_op), differential.components + ) + ) result.differentials[key] = differential.__class__(*new_comps, copy=False) return result @@ -2118,9 +2225,7 @@ class PhysicsSphericalRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'phi': Angle, - 'theta': Angle, - 'r': u.Quantity} + attr_classes = {"phi": Angle, "theta": Angle, "r": u.Quantity} def __init__(self, phi, theta=None, r=None, differentials=None, copy=True): super().__init__(phi, theta, r, copy=copy, differentials=differentials) @@ -2129,12 +2234,13 @@ def __init__(self, phi, theta=None, r=None, differentials=None, copy=True): # Note that _phi already holds our own copy if copy=True. self._phi.wrap_at(360 * u.deg, inplace=True) - if np.any(self._theta < 0.*u.deg) or np.any(self._theta > 180.*u.deg): - raise ValueError('Inclination angle(s) must be within ' - '0 deg <= angle <= 180 deg, ' - 'got {}'.format(theta.to(u.degree))) + if np.any(self._theta < 0.0 * u.deg) or np.any(self._theta > 180.0 * u.deg): + raise ValueError( + "Inclination angle(s) must be within 0 deg <= angle <= 180 deg, " + f"got {theta.to(u.degree)}" + ) - if self._r.unit.physical_type == 'length': + if self._r.unit.physical_type == "length": self._r = self._r.view(Distance) @property @@ -2162,36 +2268,46 @@ def unit_vectors(self): sinphi, cosphi = np.sin(self.phi), np.cos(self.phi) sintheta, costheta = np.sin(self.theta), np.cos(self.theta) return { - 'phi': CartesianRepresentation(-sinphi, cosphi, 0., copy=False), - 'theta': CartesianRepresentation(costheta*cosphi, - costheta*sinphi, - -sintheta, copy=False), - 'r': CartesianRepresentation(sintheta*cosphi, sintheta*sinphi, - costheta, copy=False)} + "phi": CartesianRepresentation(-sinphi, cosphi, 0.0, copy=False), + "theta": CartesianRepresentation( + costheta * cosphi, costheta * sinphi, -sintheta, copy=False + ), + "r": CartesianRepresentation( + sintheta * cosphi, sintheta * sinphi, costheta, copy=False + ), + } def scale_factors(self): r = self.r / u.radian sintheta = np.sin(self.theta) - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'phi': r * sintheta, - 'theta': r, - 'r': l} + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"phi": r * sintheta, "theta": r, "r": l} def represent_as(self, other_class, differential_class=None): # Take a short cut if the other class is a spherical representation if inspect.isclass(other_class): if issubclass(other_class, SphericalRepresentation): - diffs = self._re_represent_differentials(other_class, - differential_class) - return other_class(lon=self.phi, lat=90 * u.deg - self.theta, - distance=self.r, differentials=diffs, - copy=False) + diffs = self._re_represent_differentials( + other_class, differential_class + ) + return other_class( + lon=self.phi, + lat=90 * u.deg - self.theta, + distance=self.r, + differentials=diffs, + copy=False, + ) elif issubclass(other_class, UnitSphericalRepresentation): - diffs = self._re_represent_differentials(other_class, - differential_class) - return other_class(lon=self.phi, lat=90 * u.deg - self.theta, - differentials=diffs, copy=False) + diffs = self._re_represent_differentials( + other_class, differential_class + ) + return other_class( + lon=self.phi, + lat=90 * u.deg - self.theta, + differentials=diffs, + copy=False, + ) return super().represent_as(other_class, differential_class) @@ -2242,15 +2358,16 @@ def transform(self, matrix): """ # apply transformation in unit-spherical coordinates - xyz = erfa_ufunc.s2c(self.phi, 90*u.deg-self.theta) + xyz = erfa_ufunc.s2c(self.phi, 90 * u.deg - self.theta) p = erfa_ufunc.rxp(matrix, xyz) lon, lat, ur = erfa_ufunc.p2s(p) # `ur` is transformed unit-`r` # create transformed physics-spherical representation, # reapplying the distance scaling - rep = self.__class__(phi=lon, theta=90*u.deg-lat, r=self.r * ur) + rep = self.__class__(phi=lon, theta=90 * u.deg - lat, r=self.r * ur) - new_diffs = {k: d.transform(matrix, self, rep) - for k, d in self.differentials.items()} + new_diffs = { + k: d.transform(matrix, self, rep) for k, d in self.differentials.items() + } return rep.with_differentials(new_diffs) def norm(self): @@ -2268,20 +2385,28 @@ def norm(self): return np.abs(self.r) def _scale_operation(self, op, *args): - if any(differential.base_representation is not self.__class__ - for differential in self.differentials.values()): + if any( + differential.base_representation is not self.__class__ + for differential in self.differentials.values() + ): return super()._scale_operation(op, *args) phi_op, adjust_theta_sign, r_op = _spherical_op_funcs(op, *args) # Also run phi_op on theta to ensure theta remains between 0 and 180: # any time the scale is negative, we do -theta + 180 degrees. - result = self.__class__(phi_op(self.phi), - phi_op(adjust_theta_sign(self.theta)), - r_op(self.r), copy=False) + result = self.__class__( + phi_op(self.phi), + phi_op(adjust_theta_sign(self.theta)), + r_op(self.r), + copy=False, + ) for key, differential in self.differentials.items(): - new_comps = (op(getattr(differential, comp)) for op, comp in zip( - (operator.pos, adjust_theta_sign, r_op), - differential.components)) + new_comps = ( + op(getattr(differential, comp)) + for op, comp in zip( + (operator.pos, adjust_theta_sign, r_op), differential.components + ) + ) result.differentials[key] = differential.__class__(*new_comps, copy=False) return result @@ -2318,9 +2443,7 @@ class CylindricalRepresentation(BaseRepresentation): be references, though possibly broadcast to ensure matching shapes. """ - attr_classes = {'rho': u.Quantity, - 'phi': Angle, - 'z': u.Quantity} + attr_classes = {"rho": u.Quantity, "phi": Angle, "z": u.Quantity} def __init__(self, rho, phi=None, z=None, differentials=None, copy=True): super().__init__(rho, phi, z, copy=copy, differentials=differentials) @@ -2351,18 +2474,17 @@ def z(self): def unit_vectors(self): sinphi, cosphi = np.sin(self.phi), np.cos(self.phi) - l = np.broadcast_to(1., self.shape) + l = np.broadcast_to(1.0, self.shape) return { - 'rho': CartesianRepresentation(cosphi, sinphi, 0, copy=False), - 'phi': CartesianRepresentation(-sinphi, cosphi, 0, copy=False), - 'z': CartesianRepresentation(0, 0, l, unit=u.one, copy=False)} + "rho": CartesianRepresentation(cosphi, sinphi, 0, copy=False), + "phi": CartesianRepresentation(-sinphi, cosphi, 0, copy=False), + "z": CartesianRepresentation(0, 0, l, unit=u.one, copy=False), + } def scale_factors(self): rho = self.rho / u.radian - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'rho': l, - 'phi': rho, - 'z': l} + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"rho": l, "phi": rho, "z": l} @classmethod def from_cartesian(cls, cart): @@ -2389,18 +2511,25 @@ def to_cartesian(self): return CartesianRepresentation(x=x, y=y, z=z, copy=False) def _scale_operation(self, op, *args): - if any(differential.base_representation is not self.__class__ - for differential in self.differentials.values()): + if any( + differential.base_representation is not self.__class__ + for differential in self.differentials.values() + ): return super()._scale_operation(op, *args) phi_op, _, rho_op = _spherical_op_funcs(op, *args) z_op = lambda x: op(x, *args) - result = self.__class__(rho_op(self.rho), phi_op(self.phi), - z_op(self.z), copy=False) + result = self.__class__( + rho_op(self.rho), phi_op(self.phi), z_op(self.z), copy=False + ) for key, differential in self.differentials.items(): - new_comps = (op(getattr(differential, comp)) for op, comp in zip( - (rho_op, operator.pos, z_op), differential.components)) + new_comps = ( + op(getattr(differential, comp)) + for op, comp in zip( + (rho_op, operator.pos, z_op), differential.components + ) + ) result.differentials[key] = differential.__class__(*new_comps, copy=False) return result @@ -2441,19 +2570,23 @@ class BaseDifferential(BaseRepresentationOrDifferential): """ # Don't do anything for base helper classes. - if cls.__name__ in ('BaseDifferential', 'BaseSphericalDifferential', - 'BaseSphericalCosLatDifferential'): + if cls.__name__ in ( + "BaseDifferential", + "BaseSphericalDifferential", + "BaseSphericalCosLatDifferential", + ): return - if not hasattr(cls, 'base_representation'): - raise NotImplementedError('Differential representations must have a' - '"base_representation" class attribute.') + if not hasattr(cls, "base_representation"): + raise NotImplementedError( + "Differential representations must have a" + '"base_representation" class attribute.' + ) # If not defined explicitly, create attr_classes. - if not hasattr(cls, 'attr_classes'): + if not hasattr(cls, "attr_classes"): base_attr_classes = cls.base_representation.attr_classes - cls.attr_classes = {'d_' + c: u.Quantity - for c in base_attr_classes} + cls.attr_classes = {"d_" + c: u.Quantity for c in base_attr_classes} repr_name = cls.get_name() if repr_name in DIFFERENTIAL_CLASSES: @@ -2465,17 +2598,24 @@ class BaseDifferential(BaseRepresentationOrDifferential): # If not defined explicitly, create properties for the components. for component in cls.attr_classes: if not hasattr(cls, component): - setattr(cls, component, - property(_make_getter(component), - doc=f"Component '{component}' of the Differential.")) + setattr( + cls, + component, + property( + _make_getter(component), + doc=f"Component '{component}' of the Differential.", + ), + ) super().__init_subclass__(**kwargs) @classmethod def _check_base(cls, base): if cls not in base._compatible_differentials: - raise TypeError(f"Differential class {cls} is not compatible with the " - f"base (representation) class {base.__class__}") + raise TypeError( + f"Differential class {cls} is not compatible with the " + f"base (representation) class {base.__class__}" + ) def _get_deriv_key(self, base): """Given a base (representation instance), determine the unit of the @@ -2489,7 +2629,7 @@ def _get_deriv_key(self, base): for name in base.components: comp = getattr(base, name) - d_comp = getattr(self, f'd_{name}', None) + d_comp = getattr(self, f"d_{name}", None) if d_comp is not None: d_unit = comp.unit / d_comp.unit @@ -2501,13 +2641,13 @@ def _get_deriv_key(self, base): return str(d_unit_si) else: - raise RuntimeError("Invalid representation-differential units! This" - " likely happened because either the " - "representation or the associated differential " - "have non-standard units. Check that the input " - "positional data have positional units, and the " - "input velocity data have velocity units, or " - "are both dimensionless.") + raise RuntimeError( + "Invalid representation-differential units! This likely happened " + "because either the representation or the associated differential " + "have non-standard units. Check that the input positional data have " + "positional units, and the input velocity data have velocity units, " + "or are both dimensionless." + ) @classmethod def _get_base_vectors(cls, base): @@ -2550,8 +2690,12 @@ def to_cartesian(self, base): """ base_e, base_sf = self._get_base_vectors(base) return functools.reduce( - operator.add, (getattr(self, d_c) * base_sf[c] * base_e[c] - for d_c, c in zip(self.components, base.components))) + operator.add, + ( + getattr(self, d_c) * base_sf[c] * base_e[c] + for d_c, c in zip(self.components, base.components) + ), + ) @classmethod def from_cartesian(cls, other, base): @@ -2574,8 +2718,10 @@ def from_cartesian(cls, other, base): """ base = base.represent_as(cls.base_representation) base_e, base_sf = cls._get_base_vectors(base) - return cls(*(other.dot(e / base_sf[component]) - for component, e in base_e.items()), copy=False) + return cls( + *(other.dot(e / base_sf[component]) for component, e in base_e.items()), + copy=False, + ) def represent_as(self, other_class, base): """Convert coordinates to another representation. @@ -2617,7 +2763,8 @@ def from_representation(cls, representation, base): """ if isinstance(representation, BaseDifferential): cartesian = representation.to_cartesian( - base.represent_as(representation.base_representation)) + base.represent_as(representation.base_representation) + ) else: cartesian = representation.to_cartesian() @@ -2642,8 +2789,7 @@ class is a differential representation, the base will be converted be converted to its ``base_representation``. """ # route transformation through Cartesian - cdiff = self.represent_as(CartesianDifferential, base=base - ).transform(matrix) + cdiff = self.represent_as(CartesianDifferential, base=base).transform(matrix) # move back to original representation diff = cdiff.represent_as(self.__class__, transformed_base) return diff @@ -2687,8 +2833,9 @@ def _combine_operation(self, op, other, reverse=False): """ if isinstance(self, type(other)): first, second = (self, other) if not reverse else (other, self) - return self.__class__(*[op(getattr(first, c), getattr(second, c)) - for c in self.components]) + return self.__class__( + *[op(getattr(first, c), getattr(second, c)) for c in self.components] + ) else: try: self_cartesian = self.to_cartesian(other) @@ -2723,8 +2870,10 @@ def norm(self, base=None): """ # RadialDifferential overrides this function, so there is no handling here if not isinstance(self, CartesianDifferential) and base is None: - raise ValueError("`base` must be provided to calculate the norm of a" - f" {type(self).__name__}") + raise ValueError( + "`base` must be provided to calculate the norm of a" + f" {type(self).__name__}" + ) return self.to_cartesian(base).norm() @@ -2749,14 +2898,13 @@ class CartesianDifferential(BaseDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = CartesianRepresentation _d_xyz = None - def __init__(self, d_x, d_y=None, d_z=None, unit=None, xyz_axis=None, - copy=True): - + def __init__(self, d_x, d_y=None, d_z=None, unit=None, xyz_axis=None, copy=True): if d_y is None and d_z is None: - if isinstance(d_x, np.ndarray) and d_x.dtype.kind not in 'OV': + if isinstance(d_x, np.ndarray) and d_x.dtype.kind not in "OV": # Short-cut for 3-D array input. d_x = u.Quantity(d_x, unit, copy=copy, subok=True) # Keep a link to the array with all three coordinates @@ -2775,13 +2923,16 @@ def __init__(self, d_x, d_y=None, d_z=None, unit=None, xyz_axis=None, d_x, d_y, d_z = d_x if xyz_axis is not None: - raise ValueError("xyz_axis should only be set if d_x, d_y, and d_z " - "are in a single array passed in through d_x, " - "i.e., d_y and d_z should not be not given.") + raise ValueError( + "xyz_axis should only be set if d_x, d_y, and d_z are in a single array" + " passed in through d_x, i.e., d_y and d_z should not be not given." + ) if d_y is None or d_z is None: - raise ValueError("d_x, d_y, and d_z are required to instantiate {}" - .format(self.__class__.__name__)) + raise ValueError( + "d_x, d_y, and d_z are required to instantiate" + f" {self.__class__.__name__}" + ) if unit is not None: d_x = u.Quantity(d_x, unit, copy=copy, subok=True) @@ -2790,13 +2941,14 @@ def __init__(self, d_x, d_y=None, d_z=None, unit=None, xyz_axis=None, copy = False super().__init__(d_x, d_y, d_z, copy=copy) - if not (self._d_x.unit.is_equivalent(self._d_y.unit) and - self._d_x.unit.is_equivalent(self._d_z.unit)): - raise u.UnitsError('d_x, d_y and d_z should have equivalent units.') + if not ( + self._d_x.unit.is_equivalent(self._d_y.unit) + and self._d_x.unit.is_equivalent(self._d_z.unit) + ): + raise u.UnitsError("d_x, d_y and d_z should have equivalent units.") def to_cartesian(self, base=None): - return CartesianRepresentation(*[getattr(self, c) for c - in self.components]) + return CartesianRepresentation(*[getattr(self, c) for c in self.components]) @classmethod def from_cartesian(cls, other, base=None): @@ -2897,13 +3049,17 @@ def _combine_operation(self, op, other, reverse=False): Whether the operands should be reversed (e.g., as we got here via ``self.__rsub__`` because ``self`` is a subclass of ``other``). """ - if (isinstance(other, BaseSphericalDifferential) and - not isinstance(self, type(other)) or - isinstance(other, RadialDifferential)): + if ( + isinstance(other, BaseSphericalDifferential) + and not isinstance(self, type(other)) + or isinstance(other, RadialDifferential) + ): all_components = set(self.components) | set(other.components) first, second = (self, other) if not reverse else (other, self) - result_args = {c: op(getattr(first, c, 0.), getattr(second, c, 0.)) - for c in all_components} + result_args = { + c: op(getattr(first, c, 0.0), getattr(second, c, 0.0)) + for c in all_components + } return SphericalDifferential(**result_args) return super()._combine_operation(op, other, reverse) @@ -2920,6 +3076,7 @@ class UnitSphericalDifferential(BaseSphericalDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = UnitSphericalRepresentation @classproperty @@ -2929,7 +3086,7 @@ def _dimensional_differential(cls): def __init__(self, d_lon, d_lat=None, copy=True): super().__init__(d_lon, d_lat, copy=copy) if not self._d_lon.unit.is_equivalent(self._d_lat.unit): - raise u.UnitsError('d_lon and d_lat should have equivalent units.') + raise u.UnitsError("d_lon and d_lat should have equivalent units.") @classmethod def from_cartesian(cls, other, base): @@ -2962,8 +3119,10 @@ def from_representation(cls, representation, base=None): # though CosLat needs base for the latitude. if isinstance(representation, SphericalDifferential): return cls(representation.d_lon, representation.d_lat) - elif isinstance(representation, (SphericalCosLatDifferential, - UnitSphericalCosLatDifferential)): + elif isinstance( + representation, + (SphericalCosLatDifferential, UnitSphericalCosLatDifferential), + ): d_lon = cls._get_d_lon(representation.d_lon_coslat, base) return cls(d_lon, representation.d_lat) elif isinstance(representation, PhysicsSphericalDifferential): @@ -3025,13 +3184,14 @@ class SphericalDifferential(BaseSphericalDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = SphericalRepresentation _unit_differential = UnitSphericalDifferential def __init__(self, d_lon, d_lat=None, d_distance=None, copy=True): super().__init__(d_lon, d_lat, d_distance, copy=copy) if not self._d_lon.unit.is_equivalent(self._d_lat.unit): - raise u.UnitsError('d_lon and d_lat should have equivalent units.') + raise u.UnitsError("d_lon and d_lat should have equivalent units.") def represent_as(self, other_class, base=None): # All spherical differentials can be done without going to Cartesian, @@ -3041,8 +3201,7 @@ def represent_as(self, other_class, base=None): elif issubclass(other_class, RadialDifferential): return other_class(self.d_distance) elif issubclass(other_class, SphericalCosLatDifferential): - return other_class(self._d_lon_coslat(base), self.d_lat, - self.d_distance) + return other_class(self._d_lon_coslat(base), self.d_lat, self.d_distance) elif issubclass(other_class, UnitSphericalCosLatDifferential): return other_class(self._d_lon_coslat(base), self.d_lat) elif issubclass(other_class, PhysicsSphericalDifferential): @@ -3058,8 +3217,9 @@ def from_representation(cls, representation, base=None): d_lon = cls._get_d_lon(representation.d_lon_coslat, base) return cls(d_lon, representation.d_lat, representation.d_distance) elif isinstance(representation, PhysicsSphericalDifferential): - return cls(representation.d_phi, -representation.d_theta, - representation.d_r) + return cls( + representation.d_phi, -representation.d_theta, representation.d_r + ) return super().from_representation(representation, base) @@ -3075,6 +3235,7 @@ class BaseSphericalCosLatDifferential(BaseDifferential): With cos(lat) assumed to be included in the longitude differential. """ + @classmethod def _get_base_vectors(cls, base): """Get unit vectors and scale factors from (unit)spherical base. @@ -3148,13 +3309,17 @@ def _combine_operation(self, op, other, reverse=False): Whether the operands should be reversed (e.g., as we got here via ``self.__rsub__`` because ``self`` is a subclass of ``other``). """ - if (isinstance(other, BaseSphericalCosLatDifferential) and - not isinstance(self, type(other)) or - isinstance(other, RadialDifferential)): + if ( + isinstance(other, BaseSphericalCosLatDifferential) + and not isinstance(self, type(other)) + or isinstance(other, RadialDifferential) + ): all_components = set(self.components) | set(other.components) first, second = (self, other) if not reverse else (other, self) - result_args = {c: op(getattr(first, c, 0.), getattr(second, c, 0.)) - for c in all_components} + result_args = { + c: op(getattr(first, c, 0.0), getattr(second, c, 0.0)) + for c in all_components + } return SphericalCosLatDifferential(**result_args) return super()._combine_operation(op, other, reverse) @@ -3171,9 +3336,9 @@ class UnitSphericalCosLatDifferential(BaseSphericalCosLatDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = UnitSphericalRepresentation - attr_classes = {'d_lon_coslat': u.Quantity, - 'd_lat': u.Quantity} + attr_classes = {"d_lon_coslat": u.Quantity, "d_lat": u.Quantity} @classproperty def _dimensional_differential(cls): @@ -3182,8 +3347,7 @@ def _dimensional_differential(cls): def __init__(self, d_lon_coslat, d_lat=None, copy=True): super().__init__(d_lon_coslat, d_lat, copy=copy) if not self._d_lon_coslat.unit.is_equivalent(self._d_lat.unit): - raise u.UnitsError('d_lon_coslat and d_lat should have equivalent ' - 'units.') + raise u.UnitsError("d_lon_coslat and d_lat should have equivalent units.") @classmethod def from_cartesian(cls, other, base): @@ -3216,8 +3380,9 @@ def from_representation(cls, representation, base=None): # though w/o CosLat needs base for the latitude. if isinstance(representation, SphericalCosLatDifferential): return cls(representation.d_lon_coslat, representation.d_lat) - elif isinstance(representation, (SphericalDifferential, - UnitSphericalDifferential)): + elif isinstance( + representation, (SphericalDifferential, UnitSphericalDifferential) + ): d_lon_coslat = cls._get_d_lon_coslat(representation.d_lon, base) return cls(d_lon_coslat, representation.d_lat) elif isinstance(representation, PhysicsSphericalDifferential): @@ -3254,8 +3419,7 @@ class is a differential representation, the base will be converted else: # switch to dimensional representation du = self.d_lat.unit / base.lat.unit # derivative unit diff = self._dimensional_differential( - d_lon_coslat=self.d_lon_coslat, d_lat=self.d_lat, - d_distance=0 * du + d_lon_coslat=self.d_lon_coslat, d_lat=self.d_lat, d_distance=0 * du ).transform(matrix, base, transformed_base) return diff @@ -3280,17 +3444,19 @@ class SphericalCosLatDifferential(BaseSphericalCosLatDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = SphericalRepresentation _unit_differential = UnitSphericalCosLatDifferential - attr_classes = {'d_lon_coslat': u.Quantity, - 'd_lat': u.Quantity, - 'd_distance': u.Quantity} + attr_classes = { + "d_lon_coslat": u.Quantity, + "d_lat": u.Quantity, + "d_distance": u.Quantity, + } def __init__(self, d_lon_coslat, d_lat=None, d_distance=None, copy=True): super().__init__(d_lon_coslat, d_lat, d_distance, copy=copy) if not self._d_lon_coslat.unit.is_equivalent(self._d_lat.unit): - raise u.UnitsError('d_lon_coslat and d_lat should have equivalent ' - 'units.') + raise u.UnitsError("d_lon_coslat and d_lat should have equivalent units.") def represent_as(self, other_class, base=None): # All spherical differentials can be done without going to Cartesian, @@ -3314,18 +3480,18 @@ def from_representation(cls, representation, base=None): # though we need base for the latitude to remove coslat. if isinstance(representation, SphericalDifferential): d_lon_coslat = cls._get_d_lon_coslat(representation.d_lon, base) - return cls(d_lon_coslat, representation.d_lat, - representation.d_distance) + return cls(d_lon_coslat, representation.d_lat, representation.d_distance) elif isinstance(representation, PhysicsSphericalDifferential): d_lon_coslat = cls._get_d_lon_coslat(representation.d_phi, base) - return cls(d_lon_coslat, -representation.d_theta, - representation.d_r) + return cls(d_lon_coslat, -representation.d_theta, representation.d_r) return super().from_representation(representation, base) def _scale_operation(self, op, *args, scaled_base=False): if scaled_base: - return self.__class__(self.d_lon_coslat, self.d_lat, op(self.d_distance, *args)) + return self.__class__( + self.d_lon_coslat, self.d_lat, op(self.d_distance, *args) + ) else: return super()._scale_operation(op, *args) @@ -3341,24 +3507,27 @@ class RadialDifferential(BaseDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = RadialRepresentation def to_cartesian(self, base): - return self.d_distance * base.represent_as( - UnitSphericalRepresentation).to_cartesian() + unit_vec = base.represent_as(UnitSphericalRepresentation).to_cartesian() + return self.d_distance * unit_vec def norm(self, base=None): return self.d_distance @classmethod def from_cartesian(cls, other, base): - return cls(other.dot(base.represent_as(UnitSphericalRepresentation)), - copy=False) + return cls( + other.dot(base.represent_as(UnitSphericalRepresentation)), copy=False + ) @classmethod def from_representation(cls, representation, base=None): - if isinstance(representation, (SphericalDifferential, - SphericalCosLatDifferential)): + if isinstance( + representation, (SphericalDifferential, SphericalCosLatDifferential) + ): return cls(representation.d_distance) elif isinstance(representation, PhysicsSphericalDifferential): return cls(representation.d_r) @@ -3372,12 +3541,15 @@ def _combine_operation(self, op, other, reverse=False): else: first, second = self.d_distance, other.distance return other.__class__(op(first, second), copy=False) - elif isinstance(other, (BaseSphericalDifferential, - BaseSphericalCosLatDifferential)): + elif isinstance( + other, (BaseSphericalDifferential, BaseSphericalCosLatDifferential) + ): all_components = set(self.components) | set(other.components) first, second = (self, other) if not reverse else (other, self) - result_args = {c: op(getattr(first, c, 0.), getattr(second, c, 0.)) - for c in all_components} + result_args = { + c: op(getattr(first, c, 0.0), getattr(second, c, 0.0)) + for c in all_components + } return SphericalDifferential(**result_args) else: @@ -3397,13 +3569,13 @@ class PhysicsSphericalDifferential(BaseDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = PhysicsSphericalRepresentation def __init__(self, d_phi, d_theta=None, d_r=None, copy=True): super().__init__(d_phi, d_theta, d_r, copy=copy) if not self._d_phi.unit.is_equivalent(self._d_theta.unit): - raise u.UnitsError('d_phi and d_theta should have equivalent ' - 'units.') + raise u.UnitsError("d_phi and d_theta should have equivalent units.") def represent_as(self, other_class, base=None): # All spherical differentials can be done without going to Cartesian, @@ -3432,8 +3604,9 @@ def from_representation(cls, representation, base=None): # though we need base for the latitude to remove coslat. For that case, # do the equivalent of cls._d_lon in SphericalDifferential. if isinstance(representation, SphericalDifferential): - return cls(representation.d_lon, -representation.d_lat, - representation.d_distance) + return cls( + representation.d_lon, -representation.d_lat, representation.d_distance + ) elif isinstance(representation, SphericalCosLatDifferential): cls._check_base(base) d_phi = representation.d_lon_coslat / np.sin(base.theta) @@ -3463,6 +3636,7 @@ class CylindricalDifferential(BaseDifferential): If `True` (default), arrays will be copied. If `False`, arrays will be references, though possibly broadcast to ensure matching shapes. """ + base_representation = CylindricalRepresentation def __init__(self, d_rho, d_phi=None, d_z=None, copy=False): diff --git a/astropy/coordinates/sites.py b/astropy/coordinates/sites.py index 9c2d650205f..6f0cde6536f 100644 --- a/astropy/coordinates/sites.py +++ b/astropy/coordinates/sites.py @@ -31,6 +31,7 @@ class SiteRegistry(Mapping): lower-case, and even if you ask for something that's got mixed case, it will be interpreted as the all lower-case version. """ + def __init__(self): # the keys to this are always lower-case self._lowercase_names_to_locations = {} @@ -53,10 +54,14 @@ def __getitem__(self, site_name): """ if site_name.lower() not in self._lowercase_names_to_locations: # If site name not found, find close matches and suggest them in error - close_names = get_close_matches(site_name, self._lowercase_names_to_locations) + close_names = get_close_matches( + site_name, self._lowercase_names_to_locations + ) close_names = sorted(close_names, key=len) - raise UnknownSiteException(site_name, "the 'names' attribute", close_names=close_names) + raise UnknownSiteException( + site_name, "the 'names' attribute", close_names=close_names + ) return self._lowercase_names_to_locations[site_name.lower()] @@ -103,12 +108,14 @@ def from_json(cls, jsondb): reg = cls() for site in jsondb: site_info = jsondb[site].copy() - location = EarthLocation.from_geodetic(site_info.pop('longitude') * u.Unit(site_info.pop('longitude_unit')), - site_info.pop('latitude') * u.Unit(site_info.pop('latitude_unit')), - site_info.pop('elevation') * u.Unit(site_info.pop('elevation_unit'))) - name = site_info.pop('name') + location = EarthLocation.from_geodetic( + site_info.pop("longitude") * u.Unit(site_info.pop("longitude_unit")), + site_info.pop("latitude") * u.Unit(site_info.pop("latitude_unit")), + site_info.pop("elevation") * u.Unit(site_info.pop("elevation_unit")), + ) + name = site_info.pop("name") location.info.name = name - aliases = [alias for alias in site_info.pop('aliases') if alias] + aliases = [alias for alias in site_info.pop("aliases") if alias] if name not in aliases and name != site: aliases.append(name) location.info.meta = site_info # whatever is left @@ -124,7 +131,7 @@ def get_builtin_sites(): Load observatory database from data/observatories.json and parse them into a SiteRegistry. """ - jsondb = json.loads(get_pkg_data_contents('data/sites.json')) + jsondb = json.loads(get_pkg_data_contents("data/sites.json")) return SiteRegistry.from_json(jsondb) @@ -136,9 +143,9 @@ def get_downloaded_sites(jsonurl=None): # we explicitly set the encoding because the default is to leave it set by # the users' locale, which may fail if it's not matched to the sites.json if jsonurl is None: - content = get_pkg_data_contents('coordinates/sites.json', encoding='UTF-8') + content = get_pkg_data_contents("coordinates/sites.json", encoding="UTF-8") else: - content = get_file_contents(jsonurl, encoding='UTF-8') + content = get_file_contents(jsonurl, encoding="UTF-8") jsondb = json.loads(content) return SiteRegistry.from_json(jsondb) diff --git a/astropy/coordinates/sky_coordinate.py b/astropy/coordinates/sky_coordinate.py index 9f05612d8f5..ab475f7d0d2 100644 --- a/astropy/coordinates/sky_coordinate.py +++ b/astropy/coordinates/sky_coordinate.py @@ -31,7 +31,7 @@ _parse_coordinate_data, ) -__all__ = ['SkyCoord', 'SkyCoordInfo'] +__all__ = ["SkyCoord", "SkyCoordInfo"] class SkyCoordInfo(MixinInfo): @@ -40,21 +40,23 @@ class SkyCoordInfo(MixinInfo): required when the object is used as a mixin column within a table, but can be used as a general way to store meta information. """ - attrs_from_parent = {'unit'} # Unit is read-only + + attrs_from_parent = {"unit"} # Unit is read-only _supports_indexing = False @staticmethod def default_format(val): repr_data = val.info._repr_data - formats = ['{0.' + compname + '.value:}' for compname - in repr_data.components] - return ','.join(formats).format(repr_data) + formats = ["{0." + compname + ".value:}" for compname in repr_data.components] + return ",".join(formats).format(repr_data) @property def unit(self): repr_data = self._repr_data - unit = ','.join(str(getattr(repr_data, comp).unit) or 'None' - for comp in repr_data.components) + unit = ",".join( + str(getattr(repr_data, comp).unit) or "None" + for comp in repr_data.components + ) return unit @property @@ -63,12 +65,12 @@ def _repr_data(self): return None sc = self._parent - if (issubclass(sc.representation_type, SphericalRepresentation) - and isinstance(sc.data, UnitSphericalRepresentation)): + if issubclass(sc.representation_type, SphericalRepresentation) and isinstance( + sc.data, UnitSphericalRepresentation + ): repr_data = sc.represent_as(sc.data.__class__, in_frame_units=True) else: - repr_data = sc.represent_as(sc.representation_type, - in_frame_units=True) + repr_data = sc.represent_as(sc.representation_type, in_frame_units=True) return repr_data def _represent_as_dict(self): @@ -79,15 +81,16 @@ def _represent_as_dict(self): if isinstance(sc.data, UnitSphericalRepresentation): attrs = attrs[:-1] - diff = sc.data.differentials.get('s') + diff = sc.data.differentials.get("s") if diff is not None: - diff_attrs = list(sc.get_representation_component_names('s')) + diff_attrs = list(sc.get_representation_component_names("s")) # Don't output proper motions if they haven't been specified. if isinstance(diff, RadialDifferential): diff_attrs = diff_attrs[2:] # Don't output radial velocity unless it's actually velocity. - elif isinstance(diff, (UnitSphericalDifferential, - UnitSphericalCosLatDifferential)): + elif isinstance( + diff, (UnitSphericalDifferential, UnitSphericalCosLatDifferential) + ): diff_attrs = diff_attrs[:-1] attrs.extend(diff_attrs) @@ -95,15 +98,15 @@ def _represent_as_dict(self): out = super()._represent_as_dict(attrs) - out['representation_type'] = sc.representation_type.get_name() - out['frame'] = sc.frame.name + out["representation_type"] = sc.representation_type.get_name() + out["frame"] = sc.frame.name # Note that sc.info.unit is a fake composite unit (e.g. 'deg,deg,None' # or None,None,m) and is not stored. The individual attributes have # units. return out - def new_like(self, skycoords, length, metadata_conflicts='warn', name=None): + def new_like(self, skycoords, length, metadata_conflicts="warn", name=None): """ Return a new SkyCoord instance which is consistent with the input SkyCoord objects ``skycoords`` and has ``length`` rows. Being @@ -135,8 +138,9 @@ def new_like(self, skycoords, length, metadata_conflicts='warn', name=None): """ # Get merged info attributes like shape, dtype, format, description, etc. - attrs = self.merge_cols_attributes(skycoords, metadata_conflicts, name, - ('meta', 'description')) + attrs = self.merge_cols_attributes( + skycoords, metadata_conflicts, name, ("meta", "description") + ) skycoord0 = skycoords[0] # Make a new SkyCoord object with the desired length and attributes @@ -151,10 +155,10 @@ def new_like(self, skycoords, length, metadata_conflicts='warn', name=None): try: out[0] = skycoord[0] except Exception as err: - raise ValueError('Input skycoords are inconsistent.') from err + raise ValueError("Input skycoords are inconsistent.") from err # Set (merged) info attributes - for attr in ('name', 'meta', 'description'): + for attr in ("name", "meta", "description"): if attr in attrs: setattr(out.info, attr, attrs[attr]) @@ -287,7 +291,6 @@ class or the corresponding string alias. The frame classes that are built in info = SkyCoordInfo() def __init__(self, *args, copy=True, **kwargs): - # these are frame attributes set on this SkyCoord but *not* a part of # the frame object this SkyCoord contains self._extra_frameattr_names = set() @@ -297,9 +300,11 @@ def __init__(self, *args, copy=True, **kwargs): # to make this the fastest way to create a SkyCoord instance. Many of # the classmethods implemented for performance enhancements will use # this as the initialization path - if (len(args) == 1 and len(kwargs) == 0 - and isinstance(args[0], (BaseCoordinateFrame, SkyCoord))): - + if ( + len(args) == 1 + and len(kwargs) == 0 + and isinstance(args[0], (BaseCoordinateFrame, SkyCoord)) + ): coords = args[0] if isinstance(coords, SkyCoord): self._extra_frameattr_names = coords._extra_frameattr_names @@ -313,8 +318,10 @@ def __init__(self, *args, copy=True, **kwargs): coords = coords.frame if not coords.has_data: - raise ValueError('Cannot initialize from a coordinate frame ' - 'instance without coordinate data') + raise ValueError( + "Cannot initialize from a coordinate frame " + "instance without coordinate data" + ) if copy: self._sky_coord_frame = coords.copy() @@ -332,7 +339,8 @@ def __init__(self, *args, copy=True, **kwargs): # creating the internal self._sky_coord_frame object args = list(args) # Make it mutable skycoord_kwargs, components, info = _parse_coordinate_data( - frame_cls(**frame_kwargs), args, kwargs) + frame_cls(**frame_kwargs), args, kwargs + ) # In the above two parsing functions, these kwargs were identified # as valid frame attributes for *some* frame, but not the frame that @@ -350,7 +358,7 @@ def __init__(self, *args, copy=True, **kwargs): self._sky_coord_frame = frame_cls(copy=copy, **frame_kwargs) if not self._sky_coord_frame.has_data: - raise ValueError('Cannot create a SkyCoord without data') + raise ValueError("Cannot create a SkyCoord without data") @property def frame(self): @@ -396,12 +404,13 @@ def __eq__(self, value): # Make sure that any extra frame attribute names are equivalent. for attr in self._extra_frameattr_names | value._extra_frameattr_names: - if not self.frame._frameattr_equiv(getattr(self, attr), - getattr(value, attr)): - raise ValueError(f"cannot compare: extra frame attribute " - f"'{attr}' is not equivalent " - f"(perhaps compare the frames directly to avoid " - f"this exception)") + if not self.frame._frameattr_equiv( + getattr(self, attr), getattr(value, attr) + ): + raise ValueError( + f"cannot compare: extra frame attribute '{attr}' is not equivalent" + " (perhaps compare the frames directly to avoid this exception)" + ) return self._sky_coord_frame == value._sky_coord_frame @@ -433,6 +442,7 @@ def _apply(self, method, *args, **kwargs): **kwargs : dict Any keyword arguments for ``method``. """ + def apply_method(value): if isinstance(value, ShapedLikeNDArray): return value._apply(method, *args, **kwargs) @@ -444,24 +454,23 @@ def apply_method(value): # create a new but empty instance, and copy over stuff new = super().__new__(self.__class__) - new._sky_coord_frame = self._sky_coord_frame._apply(method, - *args, **kwargs) + new._sky_coord_frame = self._sky_coord_frame._apply(method, *args, **kwargs) new._extra_frameattr_names = self._extra_frameattr_names.copy() for attr in self._extra_frameattr_names: value = getattr(self, attr) - if getattr(value, 'shape', ()): + if getattr(value, "shape", ()): value = apply_method(value) - elif method == 'copy' or method == 'flatten': + elif method == "copy" or method == "flatten": # flatten should copy also for a single element array, but # we cannot use it directly for array scalars, since it # always returns a one-dimensional array. So, just copy. value = copy.copy(value) - setattr(new, '_' + attr, value) + setattr(new, "_" + attr, value) # Copy other 'info' attr only if it has actually been defined. # See PR #3898 for further explanation and justification, along # with Quantity.__array_finalize__ - if 'info' in self.__dict__: + if "info" in self.__dict__: new.info = self.info return new @@ -483,15 +492,17 @@ def __setitem__(self, item, value): self.frame.data[item] = value.frame.data """ if self.__class__ is not value.__class__: - raise TypeError(f'can only set from object of same class: ' - f'{self.__class__.__name__} vs. ' - f'{value.__class__.__name__}') + raise TypeError( + "can only set from object of same class: " + f"{self.__class__.__name__} vs. {value.__class__.__name__}" + ) # Make sure that any extra frame attribute names are equivalent. for attr in self._extra_frameattr_names | value._extra_frameattr_names: - if not self.frame._frameattr_equiv(getattr(self, attr), - getattr(value, attr)): - raise ValueError(f'attribute {attr} is not equivalent') + if not self.frame._frameattr_equiv( + getattr(self, attr), getattr(value, attr) + ): + raise ValueError(f"attribute {attr} is not equivalent") # Set the frame values. This checks frame equivalence and also clears # the cache to ensure that the object is not in an inconsistent state. @@ -531,18 +542,20 @@ def insert(self, obj, values, axis=0): try: idx0 = operator.index(obj) except TypeError: - raise TypeError('obj arg must be an integer') + raise TypeError("obj arg must be an integer") if axis != 0: - raise ValueError('axis must be 0') + raise ValueError("axis must be 0") if not self.shape: - raise TypeError('cannot insert into scalar {} object' - .format(self.__class__.__name__)) + raise TypeError( + f"cannot insert into scalar {self.__class__.__name__} object" + ) if abs(idx0) > len(self): - raise IndexError('index {} is out of bounds for axis 0 with size {}' - .format(idx0, len(self))) + raise IndexError( + f"index {idx0} is out of bounds for axis 0 with size {len(self)}" + ) # Turn negative index into positive if idx0 < 0: @@ -552,13 +565,15 @@ def insert(self, obj, values, axis=0): # Finally make the new object with the correct length and set values for the # three sections, before insert, the insert, and after the insert. - out = self.__class__.info.new_like([self], len(self) + n_values, name=self.info.name) + out = self.__class__.info.new_like( + [self], len(self) + n_values, name=self.info.name + ) # Set the output values. This is where validation of `values` takes place to ensure # that it can indeed be inserted. out[:idx0] = self[:idx0] - out[idx0:idx0 + n_values] = values - out[idx0 + n_values:] = self[idx0:] + out[idx0 : idx0 + n_values] = values + out[idx0 + n_values :] = self[idx0:] return out @@ -595,8 +610,9 @@ def is_transformable_to(self, new_frame): different attributes. """ # TODO! like matplotlib, do string overrides for modified methods - new_frame = (_get_frame_class(new_frame) if isinstance(new_frame, str) - else new_frame) + new_frame = ( + _get_frame_class(new_frame) if isinstance(new_frame, str) else new_frame + ) return self.frame.is_transformable_to(new_frame) def transform_to(self, frame, merge_attributes=True): @@ -658,23 +674,25 @@ def transform_to(self, frame, merge_attributes=True): for attr in frame_transform_graph.frame_attributes: self_val = getattr(self, attr, None) frame_val = getattr(frame, attr, None) - if (frame_val is not None - and not (merge_attributes - and frame.is_frame_attr_default(attr))): + if frame_val is not None and not ( + merge_attributes and frame.is_frame_attr_default(attr) + ): frame_kwargs[attr] = frame_val - elif (self_val is not None - and not self.is_frame_attr_default(attr)): + elif self_val is not None and not self.is_frame_attr_default(attr): frame_kwargs[attr] = self_val elif frame_val is not None: frame_kwargs[attr] = frame_val else: - raise ValueError('Transform `frame` must be a frame name, class, or instance') + raise ValueError( + "Transform `frame` must be a frame name, class, or instance" + ) # Get the composite transform to the new frame trans = frame_transform_graph.get_transform(self.frame.__class__, new_frame_cls) if trans is None: - raise ConvertError('Cannot transform from {} to {}' - .format(self.frame.__class__, new_frame_cls)) + raise ConvertError( + f"Cannot transform from {self.frame.__class__} to {new_frame_cls}" + ) # Make a generic frame which will accept all the frame kwargs that # are provided and allow for transforming through intermediate frames @@ -687,7 +705,7 @@ def transform_to(self, frame, merge_attributes=True): # Finally make the new SkyCoord object from the `new_coord` and # remaining frame_kwargs that are not frame_attributes in `new_coord`. - for attr in (set(new_coord.frame_attributes) & set(frame_kwargs.keys())): + for attr in set(new_coord.frame_attributes) & set(frame_kwargs.keys()): frame_kwargs.pop(attr) # Always remove the origin frame attribute, as that attribute only makes @@ -695,7 +713,7 @@ def transform_to(self, frame, merge_attributes=True): # See gh-11277. # TODO: Should it be a property of the frame attribute that it can # or cannot be stored on a SkyCoord? - frame_kwargs.pop('origin', None) + frame_kwargs.pop("origin", None) return self.__class__(new_coord, **frame_kwargs) @@ -731,31 +749,30 @@ def apply_space_motion(self, new_obstime=None, dt=None): """ from .builtin_frames.icrs import ICRS - if (new_obstime is None and dt is None or - new_obstime is not None and dt is not None): - raise ValueError("You must specify one of `new_obstime` or `dt`, " - "but not both.") + if (new_obstime is None) == (dt is None): + raise ValueError( + "You must specify one of `new_obstime` or `dt`, but not both." + ) # Validate that we have velocity info - if 's' not in self.frame.data.differentials: - raise ValueError('SkyCoord requires velocity data to evolve the ' - 'position.') - - if 'obstime' in self.frame.frame_attributes: - raise NotImplementedError("Updating the coordinates in a frame " - "with explicit time dependence is " - "currently not supported. If you would " - "like this functionality, please open an " - "issue on github:\n" - "https://github.com/astropy/astropy") + if "s" not in self.frame.data.differentials: + raise ValueError("SkyCoord requires velocity data to evolve the position.") + + if "obstime" in self.frame.frame_attributes: + raise NotImplementedError( + "Updating the coordinates in a frame with explicit time dependence is" + " currently not supported. If you would like this functionality, please" + " open an issue on github:\nhttps://github.com/astropy/astropy" + ) if new_obstime is not None and self.obstime is None: # If no obstime is already on this object, raise an error if a new # obstime is passed: we need to know the time / epoch at which the # the position / velocity were measured initially - raise ValueError('This object has no associated `obstime`. ' - 'apply_space_motion() must receive a time ' - 'difference, `dt`, and not a new obstime.') + raise ValueError( + "This object has no associated `obstime`. apply_space_motion() must" + " receive a time difference, `dt`, and not a new obstime." + ) # Compute t1 and t2, the times used in the starpm call, which *only* # uses them to compute a delta-time @@ -771,7 +788,7 @@ def apply_space_motion(self, new_obstime=None, dt=None): # assume J2000 to do the dt offset. This is not actually used # for anything except a delta-t in starpm, so it's OK that it's # not necessarily the "real" obstime - t1 = Time('J2000') + t1 = Time("J2000") new_obstime = None # we don't actually know the initial obstime t2 = t1 + dt else: @@ -785,43 +802,54 @@ def apply_space_motion(self, new_obstime=None, dt=None): # erfa function eraStarpv, comment (4). So we convert to the regular # spherical differentials. icrsrep = self.icrs.represent_as(SphericalRepresentation, SphericalDifferential) - icrsvel = icrsrep.differentials['s'] + icrsvel = icrsrep.differentials["s"] parallax_zero = False try: plx = icrsrep.distance.to_value(u.arcsecond, u.parallax()) except u.UnitConversionError: # No distance: set to 0 by convention - plx = 0. + plx = 0.0 parallax_zero = True try: - rv = icrsvel.d_distance.to_value(u.km/u.s) + rv = icrsvel.d_distance.to_value(u.km / u.s) except u.UnitConversionError: # No RV - rv = 0. - - starpm = erfa.pmsafe(icrsrep.lon.radian, icrsrep.lat.radian, - icrsvel.d_lon.to_value(u.radian/u.yr), - icrsvel.d_lat.to_value(u.radian/u.yr), - plx, rv, t1.jd1, t1.jd2, t2.jd1, t2.jd2) + rv = 0.0 + + starpm = erfa.pmsafe( + icrsrep.lon.radian, + icrsrep.lat.radian, + icrsvel.d_lon.to_value(u.radian / u.yr), + icrsvel.d_lat.to_value(u.radian / u.yr), + plx, + rv, + t1.jd1, + t1.jd2, + t2.jd1, + t2.jd2, + ) if parallax_zero: new_distance = None else: new_distance = Distance(parallax=starpm[4] << u.arcsec) - icrs2 = ICRS(ra=u.Quantity(starpm[0], u.radian, copy=False), - dec=u.Quantity(starpm[1], u.radian, copy=False), - pm_ra=u.Quantity(starpm[2], u.radian/u.yr, copy=False), - pm_dec=u.Quantity(starpm[3], u.radian/u.yr, copy=False), - distance=new_distance, - radial_velocity=u.Quantity(starpm[5], u.km/u.s, copy=False), - differential_type=SphericalDifferential) + icrs2 = ICRS( + ra=u.Quantity(starpm[0], u.radian, copy=False), + dec=u.Quantity(starpm[1], u.radian, copy=False), + pm_ra=u.Quantity(starpm[2], u.radian / u.yr, copy=False), + pm_dec=u.Quantity(starpm[3], u.radian / u.yr, copy=False), + distance=new_distance, + radial_velocity=u.Quantity(starpm[5], u.km / u.s, copy=False), + differential_type=SphericalDifferential, + ) # Update the obstime of the returned SkyCoord, and need to carry along # the frame attributes - frattrs = {attrnm: getattr(self, attrnm) - for attrnm in self._extra_frameattr_names} - frattrs['obstime'] = new_obstime + frattrs = { + attrnm: getattr(self, attrnm) for attrnm in self._extra_frameattr_names + } + frattrs["obstime"] = new_obstime result = self.__class__(icrs2, **frattrs).transform_to(self.frame) # Without this the output might not have the right differential type. @@ -834,15 +862,16 @@ def _is_name(self, string): """ Returns whether a string is one of the aliases for the frame. """ - return (self.frame.name == string or - (isinstance(self.frame.name, list) and string in self.frame.name)) + return self.frame.name == string or ( + isinstance(self.frame.name, list) and string in self.frame.name + ) def __getattr__(self, attr): """ Overrides getattr to return coordinates that this can be transformed to, based on the alias attr in the primary transform graph. """ - if '_sky_coord_frame' in self.__dict__: + if "_sky_coord_frame" in self.__dict__: if self._is_name(attr): return self # Should this be a deepcopy of self? @@ -853,11 +882,11 @@ def __getattr__(self, attr): if attr in self.frame.frame_attributes: return getattr(self.frame, attr) else: - return getattr(self, '_' + attr, None) + return getattr(self, "_" + attr, None) # Some attributes might not fall in the above category but still # are available through self._sky_coord_frame. - if not attr.startswith('_') and hasattr(self._sky_coord_frame, attr): + if not attr.startswith("_") and hasattr(self._sky_coord_frame, attr): return getattr(self._sky_coord_frame, attr) # Try to interpret as a new frame for transforming. @@ -866,16 +895,17 @@ def __getattr__(self, attr): return self.transform_to(attr) # Fail - raise AttributeError("'{}' object has no attribute '{}'" - .format(self.__class__.__name__, attr)) + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{attr}'" + ) def __setattr__(self, attr, val): # This is to make anything available through __getattr__ immutable - if '_sky_coord_frame' in self.__dict__: + if "_sky_coord_frame" in self.__dict__: if self._is_name(attr): raise AttributeError(f"'{attr}' is immutable") - if not attr.startswith('_') and hasattr(self._sky_coord_frame, attr): + if not attr.startswith("_") and hasattr(self._sky_coord_frame, attr): setattr(self._sky_coord_frame, attr, val) return @@ -886,7 +916,7 @@ def __setattr__(self, attr, val): if attr in frame_transform_graph.frame_attributes: # All possible frame attributes can be set, but only via a private # variable. See __getattr__ above. - super().__setattr__('_' + attr, val) + super().__setattr__("_" + attr, val) # Validate it frame_transform_graph.frame_attributes[attr].__get__(self) # And add to set of extra attributes @@ -898,12 +928,11 @@ def __setattr__(self, attr, val): def __delattr__(self, attr): # mirror __setattr__ above - if '_sky_coord_frame' in self.__dict__: + if "_sky_coord_frame" in self.__dict__: if self._is_name(attr): raise AttributeError(f"'{attr}' is immutable") - if not attr.startswith('_') and hasattr(self._sky_coord_frame, - attr): + if not attr.startswith("_") and hasattr(self._sky_coord_frame, attr): delattr(self._sky_coord_frame, attr) return @@ -914,7 +943,7 @@ def __delattr__(self, attr): if attr in frame_transform_graph.frame_attributes: # All possible frame attributes can be deleted, but need to remove # the corresponding private variable. See __getattr__ above. - super().__delattr__('_' + attr) + super().__delattr__("_" + attr) # Also remove it from the set of extra attributes self._extra_frameattr_names -= {attr} @@ -937,7 +966,9 @@ def __dir__(self): dir_values.add(name) # Add public attributes of self.frame - dir_values.update({attr for attr in dir(self.frame) if not attr.startswith('_')}) + dir_values.update( + {attr for attr in dir(self.frame) if not attr.startswith("_")} + ) # Add all possible frame attributes dir_values.update(frame_transform_graph.frame_attributes.keys()) @@ -949,15 +980,15 @@ def __repr__(self): coonm = self.frame.__class__.__name__ frameattrs = self.frame._frame_attrs_repr() if frameattrs: - frameattrs = ': ' + frameattrs + frameattrs = ": " + frameattrs data = self.frame._data_repr() if data: - data = ': ' + data + data = ": " + data - return f'<{clsnm} ({coonm}{frameattrs}){data}>' + return f"<{clsnm} ({coonm}{frameattrs}){data}>" - def to_string(self, style='decimal', **kwargs): + def to_string(self, style="decimal", **kwargs): """ A string representation of the coordinates. @@ -987,20 +1018,24 @@ def to_string(self, style='decimal', **kwargs): sph_coord = self.frame.represent_as(SphericalRepresentation) - styles = {'hmsdms': {'lonargs': {'unit': u.hour, 'pad': True}, - 'latargs': {'unit': u.degree, 'pad': True, 'alwayssign': True}}, - 'dms': {'lonargs': {'unit': u.degree}, - 'latargs': {'unit': u.degree}}, - 'decimal': {'lonargs': {'unit': u.degree, 'decimal': True}, - 'latargs': {'unit': u.degree, 'decimal': True}} - } + styles = { + "hmsdms": { + "lonargs": {"unit": u.hour, "pad": True}, + "latargs": {"unit": u.degree, "pad": True, "alwayssign": True}, + }, + "dms": {"lonargs": {"unit": u.degree}, "latargs": {"unit": u.degree}}, + "decimal": { + "lonargs": {"unit": u.degree, "decimal": True}, + "latargs": {"unit": u.degree, "decimal": True}, + }, + } lonargs = {} latargs = {} if style in styles: - lonargs.update(styles[style]['lonargs']) - latargs.update(styles[style]['latargs']) + lonargs.update(styles[style]["lonargs"]) + latargs.update(styles[style]["latargs"]) else: raise ValueError(f"Invalid style. Valid options are: {','.join(styles)}") @@ -1008,13 +1043,16 @@ def to_string(self, style='decimal', **kwargs): latargs.update(kwargs) if np.isscalar(sph_coord.lon.value): - coord_string = (sph_coord.lon.to_string(**lonargs) + - " " + sph_coord.lat.to_string(**latargs)) + coord_string = ( + f"{sph_coord.lon.to_string(**lonargs)}" + f" {sph_coord.lat.to_string(**latargs)}" + ) else: coord_string = [] for lonangle, latangle in zip(sph_coord.lon.ravel(), sph_coord.lat.ravel()): - coord_string += [(lonangle.to_string(**lonargs) + - " " + latangle.to_string(**latargs))] + coord_string += [ + f"{lonangle.to_string(**lonargs)} {latangle.to_string(**latargs)}" + ] if len(sph_coord.shape) > 1: coord_string = np.array(coord_string).reshape(sph_coord.shape) @@ -1056,7 +1094,7 @@ def to_table(self): # table, and the other attributes as table metadata. This matches # table.serialize._represent_mixin_as_column(). for key, value in self_as_dict.items(): - if getattr(value, 'shape', ())[:1] == (len(self),): + if getattr(value, "shape", ())[:1] == (len(self),): tabledata[key] = value else: metadata[key] = value @@ -1094,14 +1132,16 @@ def is_equivalent_frame(self, other): return False for fattrnm in frame_transform_graph.frame_attributes: - if not BaseCoordinateFrame._frameattr_equiv(getattr(self, fattrnm), - getattr(other, fattrnm)): + if not BaseCoordinateFrame._frameattr_equiv( + getattr(self, fattrnm), getattr(other, fattrnm) + ): return False return True else: # not a BaseCoordinateFrame nor a SkyCoord object - raise TypeError("Tried to do is_equivalent_frame on something that " - "isn't frame-like") + raise TypeError( + "Tried to do is_equivalent_frame on something that isn't frame-like" + ) # High-level convenience methods def separation(self, other): @@ -1142,11 +1182,15 @@ def separation(self, other): if not self.is_equivalent_frame(other): try: - kwargs = {'merge_attributes': False} if isinstance(other, SkyCoord) else {} + kwargs = ( + {"merge_attributes": False} if isinstance(other, SkyCoord) else {} + ) other = other.transform_to(self, **kwargs) except TypeError: - raise TypeError('Can only get separation to another SkyCoord ' - 'or a coordinate frame with data') + raise TypeError( + "Can only get separation to another SkyCoord " + "or a coordinate frame with data" + ) lon1 = self.spherical.lon lat1 = self.spherical.lat @@ -1182,18 +1226,25 @@ def separation_3d(self, other): """ if not self.is_equivalent_frame(other): try: - kwargs = {'merge_attributes': False} if isinstance(other, SkyCoord) else {} + kwargs = ( + {"merge_attributes": False} if isinstance(other, SkyCoord) else {} + ) other = other.transform_to(self, **kwargs) except TypeError: - raise TypeError('Can only get separation to another SkyCoord ' - 'or a coordinate frame with data') + raise TypeError( + "Can only get separation to another SkyCoord " + "or a coordinate frame with data" + ) if issubclass(self.data.__class__, UnitSphericalRepresentation): - raise ValueError('This object does not have a distance; cannot ' - 'compute 3d separation.') + raise ValueError( + "This object does not have a distance; cannot compute 3d separation." + ) if issubclass(other.data.__class__, UnitSphericalRepresentation): - raise ValueError('The other object does not have a distance; ' - 'cannot compute 3d separation.') + raise ValueError( + "The other object does not have a distance; " + "cannot compute 3d separation." + ) c1 = self.cartesian.without_differentials() c2 = other.cartesian.without_differentials() @@ -1242,7 +1293,9 @@ def spherical_offsets_to(self, tocoord): """ if not self.is_equivalent_frame(tocoord): - raise ValueError('Tried to use spherical_offsets_to with two non-matching frames!') + raise ValueError( + "Tried to use spherical_offsets_to with two non-matching frames!" + ) aframe = self.skyoffset_frame() acoord = tocoord.transform_to(aframe) @@ -1288,8 +1341,10 @@ def spherical_offsets_by(self, d_lon, d_lat): directional_offset_by : offset a coordinate by an angle in a direction """ from .builtin_frames.skyoffset import SkyOffsetFrame + return self.__class__( - SkyOffsetFrame(d_lon, d_lat, origin=self.frame).transform_to(self)) + SkyOffsetFrame(d_lon, d_lat, origin=self.frame).transform_to(self) + ) def directional_offset_by(self, position_angle, separation): """ @@ -1333,8 +1388,8 @@ def directional_offset_by(self, position_angle, separation): slon = self.represent_as(UnitSphericalRepresentation).lon newlon, newlat = angle_utilities.offset_by( - lon=slon, lat=slat, - posang=position_angle, distance=separation) + lon=slon, lat=slat, posang=position_angle, distance=separation + ) return SkyCoord(newlon, newlat, frame=self.frame) @@ -1389,14 +1444,18 @@ def match_to_catalog_sky(self, catalogcoord, nthneighbor=1): """ from .matching import match_coordinates_sky - if not (isinstance(catalogcoord, (SkyCoord, BaseCoordinateFrame)) - and catalogcoord.has_data): - raise TypeError('Can only get separation to another SkyCoord or a ' - 'coordinate frame with data') - - res = match_coordinates_sky(self, catalogcoord, - nthneighbor=nthneighbor, - storekdtree='_kdtree_sky') + if not ( + isinstance(catalogcoord, (SkyCoord, BaseCoordinateFrame)) + and catalogcoord.has_data + ): + raise TypeError( + "Can only get separation to another SkyCoord or a " + "coordinate frame with data" + ) + + res = match_coordinates_sky( + self, catalogcoord, nthneighbor=nthneighbor, storekdtree="_kdtree_sky" + ) return res def match_to_catalog_3d(self, catalogcoord, nthneighbor=1): @@ -1452,14 +1511,18 @@ def match_to_catalog_3d(self, catalogcoord, nthneighbor=1): """ from .matching import match_coordinates_3d - if not (isinstance(catalogcoord, (SkyCoord, BaseCoordinateFrame)) - and catalogcoord.has_data): - raise TypeError('Can only get separation to another SkyCoord or a ' - 'coordinate frame with data') - - res = match_coordinates_3d(self, catalogcoord, - nthneighbor=nthneighbor, - storekdtree='_kdtree_3d') + if not ( + isinstance(catalogcoord, (SkyCoord, BaseCoordinateFrame)) + and catalogcoord.has_data + ): + raise TypeError( + "Can only get separation to another SkyCoord or a " + "coordinate frame with data" + ) + + res = match_coordinates_3d( + self, catalogcoord, nthneighbor=nthneighbor, storekdtree="_kdtree_3d" + ) return res @@ -1519,8 +1582,9 @@ def search_around_sky(self, searcharoundcoords, seplimit): """ from .matching import search_around_sky - return search_around_sky(searcharoundcoords, self, seplimit, - storekdtree='_kdtree_sky') + return search_around_sky( + searcharoundcoords, self, seplimit, storekdtree="_kdtree_sky" + ) def search_around_3d(self, searcharoundcoords, distlimit): """ @@ -1578,8 +1642,9 @@ def search_around_3d(self, searcharoundcoords, distlimit): """ from .matching import search_around_3d - return search_around_3d(searcharoundcoords, self, distlimit, - storekdtree='_kdtree_3d') + return search_around_3d( + searcharoundcoords, self, distlimit, storekdtree="_kdtree_3d" + ) def position_angle(self, other): """ @@ -1616,8 +1681,10 @@ def position_angle(self, other): try: other = other.transform_to(self, merge_attributes=False) except TypeError: - raise TypeError('Can only get position_angle to another ' - 'SkyCoord or a coordinate frame with data') + raise TypeError( + "Can only get position_angle to another " + "SkyCoord or a coordinate frame with data" + ) slat = self.represent_as(UnitSphericalRepresentation).lat slon = self.represent_as(UnitSphericalRepresentation).lon @@ -1643,9 +1710,10 @@ def skyoffset_frame(self, rotation=None): the positive latitude (z) direction in the final frame. """ from .builtin_frames.skyoffset import SkyOffsetFrame + return SkyOffsetFrame(origin=self, rotation=rotation) - def get_constellation(self, short_name=False, constellation_list='iau'): + def get_constellation(self, short_name=False, constellation_list="iau"): """ Determines the constellation(s) of the coordinates this `SkyCoord` contains. @@ -1682,17 +1750,17 @@ def get_constellation(self, short_name=False, constellation_list='iau'): # because of issue #7028, the conversion to a PrecessedGeocentric # system fails in some cases. Work around is to drop the velocities. # they are not needed here since only position information is used - extra_frameattrs = {nm: getattr(self, nm) - for nm in self._extra_frameattr_names} - novel = SkyCoord(self.realize_frame(self.data.without_differentials()), - **extra_frameattrs) + extra_frameattrs = {nm: getattr(self, nm) for nm in self._extra_frameattr_names} + novel = SkyCoord( + self.realize_frame(self.data.without_differentials()), **extra_frameattrs + ) return get_constellation(novel, short_name, constellation_list) # the simpler version below can be used when gh-issue #7028 is resolved # return get_constellation(self, short_name, constellation_list) # WCS pixel to/from sky conversions - def to_pixel(self, wcs, origin=0, mode='all'): + def to_pixel(self, wcs, origin=0, mode="all"): """ Convert this coordinate to pixel coordinates using a `~astropy.wcs.WCS` object. @@ -1717,10 +1785,11 @@ def to_pixel(self, wcs, origin=0, mode='all'): astropy.wcs.utils.skycoord_to_pixel : the implementation of this method """ from astropy.wcs.utils import skycoord_to_pixel + return skycoord_to_pixel(self, wcs=wcs, origin=origin, mode=mode) @classmethod - def from_pixel(cls, xp, yp, wcs, origin=0, mode='all'): + def from_pixel(cls, xp, yp, wcs, origin=0, mode="all"): """ Create a new `SkyCoord` from pixel coordinates using an `~astropy.wcs.WCS` object. @@ -1749,6 +1818,7 @@ def from_pixel(cls, xp, yp, wcs, origin=0, mode='all'): astropy.wcs.utils.pixel_to_skycoord : the implementation of this method """ from astropy.wcs.utils import pixel_to_skycoord + return pixel_to_skycoord(xp, yp, wcs=wcs, origin=origin, mode=mode, cls=cls) def contained_by(self, wcs, image=None, **kwargs): @@ -1778,6 +1848,7 @@ def contained_by(self, wcs, image=None, **kwargs): xmax, ymax = wcs._naxis import warnings + with warnings.catch_warnings(): # Suppress warnings since they just mean we didn't find the coordinate warnings.simplefilter("ignore") @@ -1788,8 +1859,9 @@ def contained_by(self, wcs, image=None, **kwargs): return (x < xmax) & (x > 0) & (y < ymax) & (y > 0) - def radial_velocity_correction(self, kind='barycentric', obstime=None, - location=None): + def radial_velocity_correction( + self, kind="barycentric", obstime=None, location=None + ): """ Compute the correction required to convert a radial velocity at a given time and place on the Earth's Surface to a barycentric or heliocentric @@ -1890,45 +1962,47 @@ def radial_velocity_correction(self, kind='barycentric', obstime=None, from .solar_system import get_body_barycentric_posvel # location validation - timeloc = getattr(obstime, 'location', None) + timeloc = getattr(obstime, "location", None) if location is None: if self.location is not None: location = self.location if timeloc is not None: - raise ValueError('`location` cannot be in both the ' - 'passed-in `obstime` and this `SkyCoord` ' - 'because it is ambiguous which is meant ' - 'for the radial_velocity_correction.') + raise ValueError( + "`location` cannot be in both the passed-in `obstime` and this" + " `SkyCoord` because it is ambiguous which is meant for the" + " radial_velocity_correction." + ) elif timeloc is not None: location = timeloc else: - raise TypeError('Must provide a `location` to ' - 'radial_velocity_correction, either as a ' - 'SkyCoord frame attribute, as an attribute on ' - 'the passed in `obstime`, or in the method ' - 'call.') + raise TypeError( + "Must provide a `location` to radial_velocity_correction, either as" + " a SkyCoord frame attribute, as an attribute on the passed in" + " `obstime`, or in the method call." + ) elif self.location is not None or timeloc is not None: - raise ValueError('Cannot compute radial velocity correction if ' - '`location` argument is passed in and there is ' - 'also a `location` attribute on this SkyCoord or ' - 'the passed-in `obstime`.') + raise ValueError( + "Cannot compute radial velocity correction if `location` argument is" + " passed in and there is also a `location` attribute on this SkyCoord" + " or the passed-in `obstime`." + ) # obstime validation coo_at_rv_obstime = self # assume we need no space motion for now if obstime is None: obstime = self.obstime if obstime is None: - raise TypeError('Must provide an `obstime` to ' - 'radial_velocity_correction, either as a ' - 'SkyCoord frame attribute or in the method ' - 'call.') + raise TypeError( + "Must provide an `obstime` to radial_velocity_correction, either as" + " a SkyCoord frame attribute or in the method call." + ) elif self.obstime is not None and self.frame.data.differentials: # we do need space motion after all coo_at_rv_obstime = self.apply_space_motion(obstime) elif self.obstime is None: # warn the user if the object has differentials set - if 's' in self.data.differentials: + if "s" in self.data.differentials: warnings.warn( "SkyCoord has space motion, and therefore the specified " "position of the SkyCoord may not be the same as " @@ -1937,19 +2011,20 @@ def radial_velocity_correction(self, kind='barycentric', obstime=None, "for very high proper motions sources. If you wish to " "apply space motion of the SkyCoord to correct for this" "the `obstime` attribute of the SkyCoord must be set", - AstropyUserWarning + AstropyUserWarning, ) - pos_earth, v_earth = get_body_barycentric_posvel('earth', obstime) - if kind == 'barycentric': + pos_earth, v_earth = get_body_barycentric_posvel("earth", obstime) + if kind == "barycentric": v_origin_to_earth = v_earth - elif kind == 'heliocentric': - v_sun = get_body_barycentric_posvel('sun', obstime)[1] + elif kind == "heliocentric": + v_sun = get_body_barycentric_posvel("sun", obstime)[1] v_origin_to_earth = v_earth - v_sun else: - raise ValueError("`kind` argument to radial_velocity_correction must " - "be 'barycentric' or 'heliocentric', but got " - "'{}'".format(kind)) + raise ValueError( + "`kind` argument to radial_velocity_correction must " + f"be 'barycentric' or 'heliocentric', but got '{kind}'" + ) gcrs_p, gcrs_v = location.get_gcrs_posvel(obstime) # transforming to GCRS is not the correct thing to do here, since we don't want to @@ -1964,28 +2039,31 @@ def radial_velocity_correction(self, kind='barycentric', obstime=None, targcart = icrs_cart_novel - obs_icrs_cart targcart /= targcart.norm() - if kind == 'barycentric': + if kind == "barycentric": beta_obs = (v_origin_to_earth + gcrs_v) / speed_of_light - gamma_obs = 1 / np.sqrt(1 - beta_obs.norm()**2) + gamma_obs = 1 / np.sqrt(1 - beta_obs.norm() ** 2) gr = location.gravitational_redshift(obstime) # barycentric redshift according to eq 28 in Wright & Eastmann (2014), # neglecting Shapiro delay and effects of the star's own motion - zb = gamma_obs * (1 + beta_obs.dot(targcart)) / (1 + gr/speed_of_light) + zb = gamma_obs * (1 + beta_obs.dot(targcart)) / (1 + gr / speed_of_light) # try and get terms corresponding to stellar motion. if icrs_cart.differentials: try: ro = self.icrs.cartesian - beta_star = ro.differentials['s'].to_cartesian() / speed_of_light + beta_star = ro.differentials["s"].to_cartesian() / speed_of_light # ICRS unit vector at coordinate epoch ro = ro.without_differentials() ro /= ro.norm() zb *= (1 + beta_star.dot(ro)) / (1 + beta_star.dot(targcart)) except u.UnitConversionError: - warnings.warn("SkyCoord contains some velocity information, but not enough to " - "calculate the full space motion of the source, and so this has " - "been ignored for the purposes of calculating the radial velocity " - "correction. This can lead to errors on the order of metres/second.", - AstropyUserWarning) + warnings.warn( + "SkyCoord contains some velocity information, but not enough to" + " calculate the full space motion of the source, and so this" + " has been ignored for the purposes of calculating the radial" + " velocity correction. This can lead to errors on the order of" + " metres/second.", + AstropyUserWarning, + ) zb = zb - 1 return zb * speed_of_light @@ -2044,29 +2122,28 @@ def guess_from_table(cls, table, **coord_kwargs): """ _frame_cls, _frame_kwargs = _get_frame_without_data([], coord_kwargs) frame = _frame_cls(**_frame_kwargs) - coord_kwargs['frame'] = coord_kwargs.get('frame', frame) + coord_kwargs["frame"] = coord_kwargs.get("frame", frame) - representation_component_names = ( - set(frame.get_representation_component_names()) - .union(set(frame.get_representation_component_names("s"))) - ) + representation_component_names = set( + frame.get_representation_component_names() + ).union(set(frame.get_representation_component_names("s"))) comp_kwargs = {} for comp_name in representation_component_names: # this matches things like 'ra[...]'' but *not* 'rad'. # note that the "_" must be in there explicitly, because # "alphanumeric" usually includes underscores. - starts_with_comp = comp_name + r'(\W|\b|_)' + starts_with_comp = comp_name + r"(\W|\b|_)" # this part matches stuff like 'center_ra', but *not* # 'aura' - ends_with_comp = r'.*(\W|\b|_)' + comp_name + r'\b' + ends_with_comp = r".*(\W|\b|_)" + comp_name + r"\b" # the final regex ORs together the two patterns - rex = re.compile(rf"({starts_with_comp})|({ends_with_comp})", - re.IGNORECASE | re.UNICODE) + rex = re.compile( + rf"({starts_with_comp})|({ends_with_comp})", re.IGNORECASE | re.UNICODE + ) # find all matches - matches = {col_name for col_name in table.colnames - if rex.match(col_name)} + matches = {col_name for col_name in table.colnames if rex.match(col_name)} # now need to select among matches, also making sure we don't have # an exact match with another component @@ -2082,17 +2159,19 @@ def guess_from_table(cls, table, **coord_kwargs): col_name = matches.pop() else: raise ValueError( - 'Found at least two matches for component ' - f'"{comp_name}": "{matches}". Cannot guess coordinates ' - 'from a table with this ambiguity.') + f'Found at least two matches for component "{comp_name}":' + f' "{matches}". Cannot guess coordinates from a table with this' + " ambiguity." + ) comp_kwargs[comp_name] = table[col_name] for k, v in comp_kwargs.items(): if k in coord_kwargs: - raise ValueError('Found column "{}" in table, but it was ' - 'already provided as "{}" keyword to ' - 'guess_from_table function.'.format(v.name, k)) + raise ValueError( + f'Found column "{v.name}" in table, but it was already provided as' + ' "{k}" keyword to guess_from_table function.' + ) else: coord_kwargs[k] = v @@ -2100,7 +2179,7 @@ def guess_from_table(cls, table, **coord_kwargs): # Name resolve @classmethod - def from_name(cls, name, frame='icrs', parse=False, cache=True): + def from_name(cls, name, frame="icrs", parse=False, cache=True): """ Given a name, query the CDS name resolver to attempt to retrieve coordinate information for that object. The search database, sesame @@ -2138,7 +2217,7 @@ def from_name(cls, name, frame='icrs', parse=False, cache=True): icrs_coord = get_icrs_coordinates(name, parse, cache=cache) icrs_sky_coord = cls(icrs_coord) - if frame in ('icrs', icrs_coord.__class__): + if frame in ("icrs", icrs_coord.__class__): return icrs_sky_coord else: return icrs_sky_coord.transform_to(frame) diff --git a/astropy/coordinates/sky_coordinate_parsers.py b/astropy/coordinates/sky_coordinate_parsers.py index 6173229878c..6a357bb7348 100644 --- a/astropy/coordinates/sky_coordinate_parsers.py +++ b/astropy/coordinates/sky_coordinate_parsers.py @@ -27,12 +27,14 @@ part of creating SkyCoord objects. """ -PLUS_MINUS_RE = re.compile(r'(\+|\-)') +PLUS_MINUS_RE = re.compile(r"(\+|\-)") J_PREFIXED_RA_DEC_RE = re.compile( r"""J # J prefix ([0-9]{6,7}\.?[0-9]{0,2}) # RA as HHMMSS.ss or DDDMMSS.ss, optional decimal digits ([\+\-][0-9]{6}\.?[0-9]{0,2})\s*$ # Dec as DDMMSS.ss, optional decimal digits - """, re.VERBOSE) + """, + re.VERBOSE, +) def _get_frame_class(frame): @@ -44,25 +46,29 @@ def _get_frame_class(frame): if isinstance(frame, str): frame_names = frame_transform_graph.get_names() if frame not in frame_names: - raise ValueError('Coordinate frame name "{}" is not a known ' - 'coordinate frame ({})' - .format(frame, sorted(frame_names))) + raise ValueError( + f'Coordinate frame name "{frame}" is not a known ' + f"coordinate frame ({sorted(frame_names)})" + ) frame_cls = frame_transform_graph.lookup_name(frame) elif inspect.isclass(frame) and issubclass(frame, BaseCoordinateFrame): frame_cls = frame else: - raise ValueError("Coordinate frame must be a frame name or frame " - "class, not a '{}'".format(frame.__class__.__name__)) + raise ValueError( + "Coordinate frame must be a frame name or frame class, not a" + f" '{frame.__class__.__name__}'" + ) return frame_cls -_conflict_err_msg = ("Coordinate attribute '{0}'={1!r} conflicts with keyword " - "argument '{0}'={2!r}. This usually means an attribute " - "was set on one of the input objects and also in the " - "keyword arguments to {3}") +_conflict_err_msg = ( + "Coordinate attribute '{0}'={1!r} conflicts with keyword argument '{0}'={2!r}. This" + " usually means an attribute was set on one of the input objects and also in the " + "keyword arguments to {3}" +) def _get_frame_without_data(args, kwargs): @@ -85,7 +91,7 @@ def _get_frame_without_data(args, kwargs): frame_cls_kwargs = {} # The first place to check: the frame could be specified explicitly - frame = kwargs.pop('frame', None) + frame = kwargs.pop("frame", None) if frame is not None: # Here the frame was explicitly passed in as a keyword argument. @@ -99,14 +105,15 @@ def _get_frame_without_data(args, kwargs): # specified in the kwargs. We preserve these extra attributes by # adding them to the kwargs dict: for attr in frame._extra_frameattr_names: - if (attr in kwargs and - np.any(getattr(frame, attr) != kwargs[attr])): + if attr in kwargs and np.any(getattr(frame, attr) != kwargs[attr]): # This SkyCoord attribute passed in with the frame= object # conflicts with an attribute passed in directly to the # SkyCoord initializer as a kwarg: - raise ValueError(_conflict_err_msg - .format(attr, getattr(frame, attr), - kwargs[attr], 'SkyCoord')) + raise ValueError( + _conflict_err_msg.format( + attr, getattr(frame, attr), kwargs[attr], "SkyCoord" + ) + ) else: kwargs[attr] = getattr(frame, attr) frame = frame.frame @@ -118,12 +125,12 @@ def _get_frame_without_data(args, kwargs): # sure that no frame attributes were specified as kwargs - this # would require a potential three-way merge: if attr in kwargs: - raise ValueError("Cannot specify frame attribute '{}' " - "directly as an argument to SkyCoord " - "because a frame instance was passed in. " - "Either pass a frame class, or modify the " - "frame attributes of the input frame " - "instance.".format(attr)) + raise ValueError( + f"Cannot specify frame attribute '{attr}' directly as an" + " argument to SkyCoord because a frame instance was passed in." + " Either pass a frame class, or modify the frame attributes of" + " the input frame instance." + ) elif not frame.is_frame_attr_default(attr): kwargs[attr] = getattr(frame, attr) @@ -131,8 +138,8 @@ def _get_frame_without_data(args, kwargs): # Make sure we propagate representation/differential _type choices, # unless these are specified directly in the kwargs: - kwargs.setdefault('representation_type', frame.representation_type) - kwargs.setdefault('differential_type', frame.differential_type) + kwargs.setdefault("representation_type", frame.representation_type) + kwargs.setdefault("differential_type", frame.differential_type) if frame_cls is None: # frame probably a string frame_cls = _get_frame_class(frame) @@ -145,8 +152,7 @@ def _get_frame_without_data(args, kwargs): # to allow the first argument to set the class. That's OK because # _parse_coordinate_arg goes and checks that the frames match between # the first and all the others - if (isinstance(arg, (Sequence, np.ndarray)) and - len(args) == 1 and len(arg) > 0): + if isinstance(arg, (Sequence, np.ndarray)) and len(args) == 1 and len(arg) > 0: arg = arg[0] coord_frame_obj = coord_frame_cls = None @@ -156,61 +162,63 @@ def _get_frame_without_data(args, kwargs): coord_frame_obj = arg.frame if coord_frame_obj is not None: coord_frame_cls = coord_frame_obj.__class__ - frame_diff = coord_frame_obj.get_representation_cls('s') + frame_diff = coord_frame_obj.get_representation_cls("s") if frame_diff is not None: # we do this check because otherwise if there's no default # differential (i.e. it is None), the code below chokes. but # None still gets through if the user *requests* it - kwargs.setdefault('differential_type', frame_diff) + kwargs.setdefault("differential_type", frame_diff) for attr in coord_frame_obj.frame_attributes: - if (attr in kwargs and - not coord_frame_obj.is_frame_attr_default(attr) and - np.any(kwargs[attr] != getattr(coord_frame_obj, attr))): - raise ValueError("Frame attribute '{}' has conflicting " - "values between the input coordinate data " - "and either keyword arguments or the " - "frame specification (frame=...): " - "{} =/= {}" - .format(attr, - getattr(coord_frame_obj, attr), - kwargs[attr])) - - elif (attr not in kwargs and - not coord_frame_obj.is_frame_attr_default(attr)): + if ( + attr in kwargs + and not coord_frame_obj.is_frame_attr_default(attr) + and np.any(kwargs[attr] != getattr(coord_frame_obj, attr)) + ): + raise ValueError( + f"Frame attribute '{attr}' has conflicting values between the" + " input coordinate data and either keyword arguments or the " + "frame specification (frame=...):" + f" {getattr(coord_frame_obj, attr)} =/= {kwargs[attr]}" + ) + + elif attr not in kwargs and not coord_frame_obj.is_frame_attr_default( + attr + ): kwargs[attr] = getattr(coord_frame_obj, attr) if coord_frame_cls is not None: if frame_cls is None: frame_cls = coord_frame_cls elif frame_cls is not coord_frame_cls: - raise ValueError("Cannot override frame='{}' of input " - "coordinate with new frame='{}'. Instead, " - "transform the coordinate." - .format(coord_frame_cls.__name__, - frame_cls.__name__)) + raise ValueError( + f"Cannot override frame='{coord_frame_cls.__name__}' of input " + f"coordinate with new frame='{frame_cls.__name__}'. Instead, " + "transform the coordinate." + ) if frame_cls is None: from .builtin_frames import ICRS + frame_cls = ICRS # By now, frame_cls should be set - if it's not, something went wrong if not issubclass(frame_cls, BaseCoordinateFrame): # We should hopefully never get here... - raise ValueError(f'Frame class has unexpected type: {frame_cls.__name__}') + raise ValueError(f"Frame class has unexpected type: {frame_cls.__name__}") for attr in frame_cls.frame_attributes: if attr in kwargs: frame_cls_kwargs[attr] = kwargs.pop(attr) - if 'representation_type' in kwargs: - frame_cls_kwargs['representation_type'] = _get_repr_cls( - kwargs.pop('representation_type')) + if "representation_type" in kwargs: + frame_cls_kwargs["representation_type"] = _get_repr_cls( + kwargs.pop("representation_type") + ) - differential_type = kwargs.pop('differential_type', None) + differential_type = kwargs.pop("differential_type", None) if differential_type is not None: - frame_cls_kwargs['differential_type'] = _get_diff_cls( - differential_type) + frame_cls_kwargs["differential_type"] = _get_diff_cls(differential_type) return frame_cls, frame_cls_kwargs @@ -253,24 +261,25 @@ def _parse_coordinate_data(frame, args, kwargs): # `pm_ra` when they really should be passing `pm_ra_cosdec`. The # extra error should only turn on when the positional representation # is spherical, and when the component 'pm_' is passed. - pm_message = '' + pm_message = "" if frame.representation_type == SphericalRepresentation: frame_names = list(frame.get_representation_component_names().keys()) lon_name = frame_names[0] lat_name = frame_names[1] - if f'pm_{lon_name}' in list(kwargs.keys()): - pm_message = ('\n\n By default, most frame classes expect ' - 'the longitudinal proper motion to include ' - 'the cos(latitude) term, named ' - '`pm_{}_cos{}`. Did you mean to pass in ' - 'this component?' - .format(lon_name, lat_name)) + if f"pm_{lon_name}" in list(kwargs.keys()): + pm_message = ( + "\n\n By default, most frame classes expect the longitudinal proper" + " motion to include the cos(latitude) term, named" + f" `pm_{lon_name}_cos{lat_name}`. Did you mean to pass in this" + " component?" + ) - raise ValueError('Unrecognized keyword argument(s) {}{}' - .format(', '.join(f"'{key}'" - for key in kwargs), - pm_message)) + raise ValueError( + "Unrecognized keyword argument(s) {}{}".format( + ", ".join(f"'{key}'" for key in kwargs), pm_message + ) + ) # Finally deal with the unnamed args. This figures out what the arg[0] # is and returns a dict with appropriate key/values for initializing @@ -284,10 +293,11 @@ def _parse_coordinate_data(frame, args, kwargs): # frame attributes like equinox or obstime which were explicitly # specified in the coordinate object (i.e. non-default). _skycoord_kwargs, _components = _parse_coordinate_arg( - args[0], frame, units, kwargs) + args[0], frame, units, kwargs + ) # Copy other 'info' attr only if it has actually been defined. - if 'info' in getattr(args[0], '__dict__', ()): + if "info" in getattr(args[0], "__dict__", ()): info = args[0].info elif len(args) <= 3: @@ -297,14 +307,16 @@ def _parse_coordinate_data(frame, args, kwargs): frame_attr_names = frame.representation_component_names.keys() repr_attr_names = frame.representation_component_names.values() - for arg, frame_attr_name, repr_attr_name, unit in zip(args, frame_attr_names, - repr_attr_names, units): + for arg, frame_attr_name, repr_attr_name, unit in zip( + args, frame_attr_names, repr_attr_names, units + ): attr_class = frame.representation_type.attr_classes[repr_attr_name] _components[frame_attr_name] = attr_class(arg, unit=unit) else: - raise ValueError('Must supply no more than three positional arguments, got {}' - .format(len(args))) + raise ValueError( + f"Must supply no more than three positional arguments, got {len(args)}" + ) # The next two loops copy the component and skycoord attribute data into # their final, respective "valid_" dictionaries. For each, we check that @@ -314,19 +326,23 @@ def _parse_coordinate_data(frame, args, kwargs): # First validate the component data for attr, coord_value in _components.items(): if attr in valid_components: - raise ValueError(_conflict_err_msg - .format(attr, coord_value, - valid_components[attr], 'SkyCoord')) + raise ValueError( + _conflict_err_msg.format( + attr, coord_value, valid_components[attr], "SkyCoord" + ) + ) valid_components[attr] = coord_value # Now validate the custom SkyCoord attributes for attr, value in _skycoord_kwargs.items(): - if (attr in valid_skycoord_kwargs and - np.any(valid_skycoord_kwargs[attr] != value)): - raise ValueError(_conflict_err_msg - .format(attr, value, - valid_skycoord_kwargs[attr], - 'SkyCoord')) + if attr in valid_skycoord_kwargs and np.any( + valid_skycoord_kwargs[attr] != value + ): + raise ValueError( + _conflict_err_msg.format( + attr, value, valid_skycoord_kwargs[attr], "SkyCoord" + ) + ) valid_skycoord_kwargs[attr] = value return valid_skycoord_kwargs, valid_components, info @@ -337,14 +353,14 @@ def _get_representation_component_units(args, kwargs): Get the unit from kwargs for the *representation* components (not the differentials). """ - if 'unit' not in kwargs: + if "unit" not in kwargs: units = [None, None, None] else: - units = kwargs.pop('unit') + units = kwargs.pop("unit") if isinstance(units, str): - units = [x.strip() for x in units.split(',')] + units = [x.strip() for x in units.split(",")] # Allow for input like unit='deg' or unit='m' if len(units) == 1: units = [units[0], units[0], units[0]] @@ -357,8 +373,10 @@ def _get_representation_component_units(args, kwargs): if len(units) > 3: raise ValueError() except Exception as err: - raise ValueError('Unit keyword must have one to three unit values as ' - 'tuple or comma-separated string.') from err + raise ValueError( + "Unit keyword must have one to three unit values as " + "tuple or comma-separated string." + ) from err return units @@ -399,7 +417,7 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): # args have the same frame as explicitly supplied, so don't worry here. if not coords.has_data: - raise ValueError('Cannot initialize from a frame without coordinate data') + raise ValueError("Cannot initialize from a frame without coordinate data") data = coords.data.represent_as(frame.representation_type) @@ -407,8 +425,10 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): repr_attr_name_to_drop = [] for repr_attr_name in repr_attr_names: # If coords did not have an explicit distance then don't include in initializers. - if (isinstance(coords.data, UnitSphericalRepresentation) and - repr_attr_name == 'distance'): + if ( + isinstance(coords.data, UnitSphericalRepresentation) + and repr_attr_name == "distance" + ): repr_attr_name_to_drop.append(repr_attr_name) continue @@ -423,13 +443,19 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): del frame_attr_names[nameidx] del repr_attr_classes[nameidx] - if coords.data.differentials and 's' in coords.data.differentials: - orig_vel = coords.data.differentials['s'] - vel = coords.data.represent_as(frame.representation_type, frame.get_representation_cls('s')).differentials['s'] - for frname, reprname in frame.get_representation_component_names('s').items(): - if (reprname == 'd_distance' and - not hasattr(orig_vel, reprname) and - 'unit' in orig_vel.get_name()): + if coords.data.differentials and "s" in coords.data.differentials: + orig_vel = coords.data.differentials["s"] + vel = coords.data.represent_as( + frame.representation_type, frame.get_representation_cls("s") + ).differentials["s"] + for frname, reprname in frame.get_representation_component_names( + "s" + ).items(): + if ( + reprname == "d_distance" + and not hasattr(orig_vel, reprname) + and "unit" in orig_vel.get_name() + ): continue values.append(getattr(vel, reprname)) units.append(None) @@ -439,29 +465,40 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): for attr in frame_transform_graph.frame_attributes: value = getattr(coords, attr, None) - use_value = (isinstance(coords, SkyCoord) or - attr not in coords.frame_attributes) + use_value = ( + isinstance(coords, SkyCoord) or attr not in coords.frame_attributes + ) if use_value and value is not None: skycoord_kwargs[attr] = value elif isinstance(coords, BaseRepresentation): - if coords.differentials and 's' in coords.differentials: - diffs = frame.get_representation_cls('s') + if coords.differentials and "s" in coords.differentials: + diffs = frame.get_representation_cls("s") data = coords.represent_as(frame.representation_type, diffs) - values = [getattr(data, repr_attr_name) for repr_attr_name in repr_attr_names] - for frname, reprname in frame.get_representation_component_names('s').items(): - values.append(getattr(data.differentials['s'], reprname)) + values = [ + getattr(data, repr_attr_name) for repr_attr_name in repr_attr_names + ] + for frname, reprname in frame.get_representation_component_names( + "s" + ).items(): + values.append(getattr(data.differentials["s"], reprname)) units.append(None) frame_attr_names.append(frname) repr_attr_names.append(reprname) - repr_attr_classes.append(data.differentials['s'].attr_classes[reprname]) + repr_attr_classes.append(data.differentials["s"].attr_classes[reprname]) else: data = coords.represent_as(frame.representation_type) - values = [getattr(data, repr_attr_name) for repr_attr_name in repr_attr_names] - - elif (isinstance(coords, np.ndarray) and coords.dtype.kind in 'if' and - coords.ndim == 2 and coords.shape[1] <= 3): + values = [ + getattr(data, repr_attr_name) for repr_attr_name in repr_attr_names + ] + + elif ( + isinstance(coords, np.ndarray) + and coords.dtype.kind in "if" + and coords.ndim == 2 + and coords.shape[1] <= 3 + ): # 2-d array of coordinate values. Handle specially for efficiency. values = coords.transpose() # Iterates over repr attrs @@ -469,8 +506,10 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): # Handles list-like input. vals = [] - is_ra_dec_representation = ('ra' in frame.representation_component_names and - 'dec' in frame.representation_component_names) + is_ra_dec_representation = ( + "ra" in frame.representation_component_names + and "dec" in frame.representation_component_names + ) coord_types = (SkyCoord, BaseCoordinateFrame, BaseRepresentation) if any(isinstance(coord, coord_types) for coord in coords): # this parsing path is used when there are coordinate-like objects @@ -481,8 +520,9 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): # Check that all frames are equivalent for sc in scs[1:]: if not sc.is_equivalent_frame(scs[0]): - raise ValueError("List of inputs don't have equivalent " - "frames: {} != {}".format(sc, scs[0])) + raise ValueError( + f"List of inputs don't have equivalent frames: {sc} != {scs[0]}" + ) # Now use the first to determine if they are all UnitSpherical allunitsphrepr = isinstance(scs[0].data, UnitSphericalRepresentation) @@ -498,14 +538,18 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): # Now combine the values, to be used below values = [] - for data_attr_name, repr_attr_name in zip(frame_attr_names, repr_attr_names): - if allunitsphrepr and repr_attr_name == 'distance': + for data_attr_name, repr_attr_name in zip( + frame_attr_names, repr_attr_names + ): + if allunitsphrepr and repr_attr_name == "distance": # if they are *all* UnitSpherical, don't give a distance continue data_vals = [] for sc in scs: data_val = getattr(sc, data_attr_name) - data_vals.append(data_val.reshape(1,) if sc.isscalar else data_val) + data_vals.append( + data_val.reshape((1,)) if sc.isscalar else data_val + ) concat_vals = np.concatenate(data_vals) # Hack because np.concatenate doesn't fully work with Quantity if isinstance(concat_vals, u.Quantity): @@ -518,7 +562,7 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): if isinstance(coord, str): coord1 = coord.split() if len(coord1) == 6: - coord = (' '.join(coord1[:3]), ' '.join(coord1[3:])) + coord = (" ".join(coord1[:3]), " ".join(coord1[3:])) elif is_ra_dec_representation: coord = _parse_ra_dec(coord) else: @@ -530,21 +574,24 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): try: n_coords = sorted({len(x) for x in vals}) except Exception as err: - raise ValueError('One or more elements of input sequence ' - 'does not have a length.') from err + raise ValueError( + "One or more elements of input sequence does not have a length." + ) from err if len(n_coords) > 1: - raise ValueError('Input coordinate values must have ' - 'same number of elements, found {}'.format(n_coords)) + raise ValueError( + "Input coordinate values must have same number of elements, found" + f" {n_coords}" + ) n_coords = n_coords[0] # Must have no more coord inputs than representation attributes if n_coords > n_attr_names: - raise ValueError('Input coordinates have {} values but ' - 'representation {} only accepts {}' - .format(n_coords, - frame.representation_type.get_name(), - n_attr_names)) + raise ValueError( + f"Input coordinates have {n_coords} values but representation" + f" {frame.representation_type.get_name()} only accepts" + f" {n_attr_names}" + ) # Now transpose vals to get [(v1_0 .. v1_N), (v2_0 .. v2_N), (v3_0 .. v3_N)] # (ok since we know it is exactly rectangular). (Note: can't just use zip(*values) @@ -555,19 +602,21 @@ def _parse_coordinate_arg(coords, frame, units, init_kwargs): if is_scalar: values = [x[0] for x in values] else: - raise ValueError('Cannot parse coordinates from first argument') + raise ValueError("Cannot parse coordinates from first argument") # Finally we have a list of values from which to create the keyword args # for the frame initialization. Validate by running through the appropriate # class initializer and supply units (which might be None). try: for frame_attr_name, repr_attr_class, value, unit in zip( - frame_attr_names, repr_attr_classes, values, units): - components[frame_attr_name] = repr_attr_class(value, unit=unit, - copy=False) + frame_attr_names, repr_attr_classes, values, units + ): + components[frame_attr_name] = repr_attr_class(value, unit=unit, copy=False) except Exception as err: - raise ValueError('Cannot parse first argument data "{}" for attribute ' - '{}'.format(value, frame_attr_name)) from err + raise ValueError( + f'Cannot parse first argument data "{value}" for attribute' + f" {frame_attr_name}" + ) from err return skycoord_kwargs, components @@ -588,18 +637,21 @@ def _get_representation_attrs(frame, units, kwargs): repr_attr_classes = frame.representation_type.attr_classes.values() valid_kwargs = {} - for frame_attr_name, repr_attr_class, unit in zip(frame_attr_names, repr_attr_classes, units): + for frame_attr_name, repr_attr_class, unit in zip( + frame_attr_names, repr_attr_classes, units + ): value = kwargs.pop(frame_attr_name, None) if value is not None: try: valid_kwargs[frame_attr_name] = repr_attr_class(value, unit=unit) except u.UnitConversionError as err: error_message = ( - f"Unit '{unit}' ({unit.physical_type}) could not be applied to '{frame_attr_name}'. " - "This can occur when passing units for some coordinate components " - "when other components are specified as Quantity objects. " - "Either pass a list of units for all components (and unit-less coordinate data), " - "or pass Quantities for all components." + f"Unit '{unit}' ({unit.physical_type}) could not be applied to" + f" '{frame_attr_name}'. This can occur when passing units for some" + " coordinate components when other components are specified as" + " Quantity objects. Either pass a list of units for all components" + " (and unit-less coordinate data), or pass Quantities for all" + " components." ) raise u.UnitConversionError(error_message) from err @@ -608,7 +660,9 @@ def _get_representation_attrs(frame, units, kwargs): differential_type = frame.differential_type if differential_type is not None: - for frame_name, repr_name in frame.get_representation_component_names('s').items(): + for frame_name, repr_name in frame.get_representation_component_names( + "s" + ).items(): diff_attr_class = differential_type.attr_classes[repr_name] value = kwargs.pop(frame_name, None) if value is not None: @@ -644,26 +698,30 @@ def _parse_ra_dec(coord_str): coord1 = coord_str.split() else: # This exception should never be raised from SkyCoord - raise TypeError('coord_str must be a single str') + raise TypeError("coord_str must be a single str") if len(coord1) == 6: - coord = (' '.join(coord1[:3]), ' '.join(coord1[3:])) + coord = (" ".join(coord1[:3]), " ".join(coord1[3:])) elif len(coord1) > 2: coord = PLUS_MINUS_RE.split(coord_str) - coord = (coord[0], ' '.join(coord[1:])) + coord = (coord[0], " ".join(coord[1:])) elif len(coord1) == 1: match_j = J_PREFIXED_RA_DEC_RE.match(coord_str) if match_j: coord = match_j.groups() - if len(coord[0].split('.')[0]) == 7: - coord = (f'{coord[0][0:3]} {coord[0][3:5]} {coord[0][5:]}', - f'{coord[1][0:3]} {coord[1][3:5]} {coord[1][5:]}') + if len(coord[0].split(".")[0]) == 7: + coord = ( + f"{coord[0][0:3]} {coord[0][3:5]} {coord[0][5:]}", + f"{coord[1][0:3]} {coord[1][3:5]} {coord[1][5:]}", + ) else: - coord = (f'{coord[0][0:2]} {coord[0][2:4]} {coord[0][4:]}', - f'{coord[1][0:3]} {coord[1][3:5]} {coord[1][5:]}') + coord = ( + f"{coord[0][0:2]} {coord[0][2:4]} {coord[0][4:]}", + f"{coord[1][0:3]} {coord[1][3:5]} {coord[1][5:]}", + ) else: coord = PLUS_MINUS_RE.split(coord_str) - coord = (coord[0], ' '.join(coord[1:])) + coord = (coord[0], " ".join(coord[1:])) else: coord = coord1 diff --git a/astropy/coordinates/solar_system.py b/astropy/coordinates/solar_system.py index 1ec8267de8f..9412b774ef9 100644 --- a/astropy/coordinates/solar_system.py +++ b/astropy/coordinates/solar_system.py @@ -23,38 +23,43 @@ from .representation import CartesianDifferential, CartesianRepresentation from .sky_coordinate import SkyCoord -__all__ = ["get_body", "get_moon", "get_body_barycentric", - "get_body_barycentric_posvel", "solar_system_ephemeris"] +__all__ = [ + "get_body", + "get_moon", + "get_body_barycentric", + "get_body_barycentric_posvel", + "solar_system_ephemeris", +] -DEFAULT_JPL_EPHEMERIS = 'de430' +DEFAULT_JPL_EPHEMERIS = "de430" """List of kernel pairs needed to calculate positions of a given object.""" BODY_NAME_TO_KERNEL_SPEC = { - 'sun': [(0, 10)], - 'mercury': [(0, 1), (1, 199)], - 'venus': [(0, 2), (2, 299)], - 'earth-moon-barycenter': [(0, 3)], - 'earth': [(0, 3), (3, 399)], - 'moon': [(0, 3), (3, 301)], - 'mars': [(0, 4)], - 'jupiter': [(0, 5)], - 'saturn': [(0, 6)], - 'uranus': [(0, 7)], - 'neptune': [(0, 8)], - 'pluto': [(0, 9)], + "sun": [(0, 10)], + "mercury": [(0, 1), (1, 199)], + "venus": [(0, 2), (2, 299)], + "earth-moon-barycenter": [(0, 3)], + "earth": [(0, 3), (3, 399)], + "moon": [(0, 3), (3, 301)], + "mars": [(0, 4)], + "jupiter": [(0, 5)], + "saturn": [(0, 6)], + "uranus": [(0, 7)], + "neptune": [(0, 8)], + "pluto": [(0, 9)], } """Indices to the plan94 routine for the given object.""" PLAN94_BODY_NAME_TO_PLANET_INDEX = { - 'mercury': 1, - 'venus': 2, - 'earth-moon-barycenter': 3, - 'mars': 4, - 'jupiter': 5, - 'saturn': 6, - 'uranus': 7, - 'neptune': 8, + "mercury": 1, + "venus": 2, + "earth-moon-barycenter": 3, + "mars": 4, + "jupiter": 5, + "saturn": 6, + "uranus": 7, + "neptune": 8, } _EPHEMERIS_NOTE = """ @@ -73,7 +78,9 @@ >>> solar_system_ephemeris.bodies ('earth', 'sun', 'moon', 'mercury', 'venus', 'earth-moon-barycenter', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune') -"""[1:-1] +"""[ + 1:-1 +] class solar_system_ephemeris(ScienceState): @@ -109,7 +116,8 @@ class solar_system_ephemeris(ScienceState): .. [2] https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/aareadme_de432s.txt .. [3] https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/a_old_versions/ """ - _value = 'builtin' + + _value = "builtin" _kernel = None @classmethod @@ -143,9 +151,10 @@ def kernel(cls): def bodies(cls): if cls._value is None: return None - if cls._value.lower() == 'builtin': - return (('earth', 'sun', 'moon') + - tuple(PLAN94_BODY_NAME_TO_PLANET_INDEX.keys())) + if cls._value.lower() == "builtin": + return ("earth", "sun", "moon") + tuple( + PLAN94_BODY_NAME_TO_PLANET_INDEX.keys() + ) else: return tuple(BODY_NAME_TO_KERNEL_SPEC.keys()) @@ -155,23 +164,26 @@ def _get_kernel(value): Try importing jplephem, download/retrieve from cache the Satellite Planet Kernel corresponding to the given ephemeris. """ - if value is None or value.lower() == 'builtin': + if value is None or value.lower() == "builtin": return None try: from jplephem.spk import SPK except ImportError: - raise ImportError("Solar system JPL ephemeris calculations require " - "the jplephem package " - "(https://pypi.org/project/jplephem/)") + raise ImportError( + "Solar system JPL ephemeris calculations require the jplephem package " + "(https://pypi.org/project/jplephem/)" + ) - if value.lower() == 'jpl': + if value.lower() == "jpl": # Get the default JPL ephemeris URL value = DEFAULT_JPL_EPHEMERIS - if re.compile(r'de[0-9][0-9][0-9]s?').match(value.lower()): - value = ('https://naif.jpl.nasa.gov/pub/naif/generic_kernels' - '/spk/planets/{:s}.bsp'.format(value.lower())) + if re.compile(r"de[0-9][0-9][0-9]s?").match(value.lower()): + value = ( + "https://naif.jpl.nasa.gov/pub/naif/generic_kernels" + f"/spk/planets/{value.lower():s}.bsp" + ) elif os.path.isfile(value): return SPK.open(value) @@ -180,14 +192,15 @@ def _get_kernel(value): try: urlparse(value) except Exception: - raise ValueError('{} was not one of the standard strings and ' - 'could not be parsed as a file path or URL'.format(value)) + raise ValueError( + f"{value} was not one of the standard strings and " + "could not be parsed as a file path or URL" + ) return SPK.open(download_file(value, cache=True)) -def _get_body_barycentric_posvel(body, time, ephemeris=None, - get_velocity=True): +def _get_body_barycentric_posvel(body, time, ephemeris=None, get_velocity=True): """Calculate the barycentric position (and velocity) of a solar system body. Parameters @@ -228,14 +241,14 @@ def _get_body_barycentric_posvel(body, time, ephemeris=None, else: kernel = _get_kernel(ephemeris) - jd1, jd2 = get_jd12(time, 'tdb') + jd1, jd2 = get_jd12(time, "tdb") if kernel is None: body = body.lower() earth_pv_helio, earth_pv_bary = erfa.epv00(jd1, jd2) - if body == 'earth': + if body == "earth": body_pv_bary = earth_pv_bary - elif body == 'moon': + elif body == "moon": # The moon98 documentation notes that it takes TT, but that TDB leads # to errors smaller than the uncertainties in the algorithm. # moon98 returns the astrometric position relative to the Earth. @@ -243,24 +256,26 @@ def _get_body_barycentric_posvel(body, time, ephemeris=None, body_pv_bary = erfa.pvppv(moon_pv_geo, earth_pv_bary) else: sun_pv_bary = erfa.pvmpv(earth_pv_bary, earth_pv_helio) - if body == 'sun': + if body == "sun": body_pv_bary = sun_pv_bary else: try: body_index = PLAN94_BODY_NAME_TO_PLANET_INDEX[body] except KeyError: - raise KeyError("{}'s position and velocity cannot be " - "calculated with the '{}' ephemeris." - .format(body, ephemeris)) + raise KeyError( + f"{body}'s position and velocity cannot be " + f"calculated with the '{ephemeris}' ephemeris." + ) body_pv_helio = erfa.plan94(jd1, jd2, body_index) body_pv_bary = erfa.pvppv(body_pv_helio, sun_pv_bary) body_pos_bary = CartesianRepresentation( - body_pv_bary['p'], unit=u.au, xyz_axis=-1, copy=False) + body_pv_bary["p"], unit=u.au, xyz_axis=-1, copy=False + ) if get_velocity: body_vel_bary = CartesianRepresentation( - body_pv_bary['v'], unit=u.au/u.day, xyz_axis=-1, - copy=False) + body_pv_bary["v"], unit=u.au / u.day, xyz_axis=-1, copy=False + ) else: if isinstance(body, str): @@ -268,21 +283,24 @@ def _get_body_barycentric_posvel(body, time, ephemeris=None, try: kernel_spec = BODY_NAME_TO_KERNEL_SPEC[body.lower()] except KeyError: - raise KeyError("{}'s position cannot be calculated with " - "the {} ephemeris.".format(body, ephemeris)) + raise KeyError( + f"{body}'s position cannot be calculated with " + f"the {ephemeris} ephemeris." + ) else: # otherwise, assume the user knows what their doing and intentionally # passed in a kernel chain kernel_spec = body # jplephem cannot handle multi-D arrays, so convert to 1D here. - jd1_shape = getattr(jd1, 'shape', ()) + jd1_shape = getattr(jd1, "shape", ()) if len(jd1_shape) > 1: jd1, jd2 = jd1.ravel(), jd2.ravel() # Note that we use the new jd1.shape here to create a 1D result array. # It is reshaped below. - body_posvel_bary = np.zeros((2 if get_velocity else 1, 3) + - getattr(jd1, 'shape', ())) + body_posvel_bary = np.zeros( + (2 if get_velocity else 1, 3) + getattr(jd1, "shape", ()) + ) for pair in kernel_spec: spk = kernel[pair] if spk.data_type == 3: @@ -297,16 +315,19 @@ def _get_body_barycentric_posvel(body, time, ephemeris=None, # derivative. If no velocities are desired, body_posvel_bary # has only one element and thus the loop ends after a single # iteration, avoiding the velocity calculation. - for body_p_or_v, p_or_v in zip(body_posvel_bary, - spk.generate(jd1, jd2)): + for body_p_or_v, p_or_v in zip( + body_posvel_bary, spk.generate(jd1, jd2) + ): body_p_or_v += p_or_v body_posvel_bary.shape = body_posvel_bary.shape[:2] + jd1_shape - body_pos_bary = CartesianRepresentation(body_posvel_bary[0], - unit=u.km, copy=False) + body_pos_bary = CartesianRepresentation( + body_posvel_bary[0], unit=u.km, copy=False + ) if get_velocity: - body_vel_bary = CartesianRepresentation(body_posvel_bary[1], - unit=u.km/u.day, copy=False) + body_vel_bary = CartesianRepresentation( + body_posvel_bary[1], unit=u.km / u.day, copy=False + ) return (body_pos_bary, body_vel_bary) if get_velocity else body_pos_bary @@ -376,8 +397,7 @@ def get_body_barycentric(body, time, ephemeris=None): ----- {_EPHEMERIS_NOTE} """ - return _get_body_barycentric_posvel(body, time, ephemeris, - get_velocity=False) + return _get_body_barycentric_posvel(body, time, ephemeris, get_velocity=False) def _get_apparent_body_position(body, time, ephemeris, obsgeoloc=None): @@ -412,18 +432,17 @@ def _get_apparent_body_position(body, time, ephemeris, obsgeoloc=None): ephemeris = solar_system_ephemeris.get() # Calculate position given approximate light travel time. - delta_light_travel_time = 20. * u.s + delta_light_travel_time = 20.0 * u.s emitted_time = time - light_travel_time = 0. * u.s - earth_loc = get_body_barycentric('earth', time, ephemeris) + light_travel_time = 0.0 * u.s + earth_loc = get_body_barycentric("earth", time, ephemeris) if obsgeoloc is not None: earth_loc += obsgeoloc - while np.any(np.fabs(delta_light_travel_time) > 1.0e-8*u.s): + while np.any(np.fabs(delta_light_travel_time) > 1.0e-8 * u.s): body_loc = get_body_barycentric(body, emitted_time, ephemeris) earth_distance = (body_loc - earth_loc).norm() - delta_light_travel_time = (light_travel_time - - earth_distance/speed_of_light) - light_travel_time = earth_distance/speed_of_light + delta_light_travel_time = light_travel_time - earth_distance / speed_of_light + light_travel_time = earth_distance / speed_of_light emitted_time = time - light_travel_time return get_body_barycentric(body, emitted_time, ephemeris) @@ -474,9 +493,9 @@ def get_body(body, time, location=None, ephemeris=None): cartrep = _get_apparent_body_position(body, time, ephemeris, obsgeoloc) icrs = ICRS(cartrep) - gcrs = icrs.transform_to(GCRS(obstime=time, - obsgeoloc=obsgeoloc, - obsgeovel=obsgeovel)) + gcrs = icrs.transform_to( + GCRS(obstime=time, obsgeoloc=obsgeoloc, obsgeovel=obsgeovel) + ) return SkyCoord(gcrs) @@ -513,13 +532,16 @@ def get_moon(time, location=None, ephemeris=None): {_EPHEMERIS_NOTE} """ - return get_body('moon', time, location=location, ephemeris=ephemeris) + return get_body("moon", time, location=location, ephemeris=ephemeris) # Add note about the ephemeris choices to the docstrings of relevant functions. # Note: sadly, one cannot use f-strings for docstrings, so we format explicitly. -for f in [f for f in locals().values() if callable(f) and f.__doc__ is not None - and '{_EPHEMERIS_NOTE}' in f.__doc__]: +for f in [ + f + for f in locals().values() + if callable(f) and f.__doc__ is not None and "{_EPHEMERIS_NOTE}" in f.__doc__ +]: f.__doc__ = f.__doc__.format(_EPHEMERIS_NOTE=indent(_EPHEMERIS_NOTE)[4:]) @@ -530,18 +552,21 @@ def get_moon(time, location=None, ephemeris=None): """ -@deprecated('4.2', deprecation_msg) +@deprecated("4.2", deprecation_msg) def _apparent_position_in_true_coordinates(skycoord): """ Convert Skycoord in GCRS frame into one in which RA and Dec are defined w.r.t to the true equinox and poles of the Earth """ - location = getattr(skycoord, 'location', None) + location = getattr(skycoord, "location", None) if location is None: gcrs_rep = skycoord.obsgeoloc.with_differentials( - {'s': CartesianDifferential.from_cartesian(skycoord.obsgeovel)}) - location = (GCRS(gcrs_rep, obstime=skycoord.obstime) - .transform_to(ITRS(obstime=skycoord.obstime)) - .earth_location) + {"s": CartesianDifferential.from_cartesian(skycoord.obsgeovel)} + ) + location = ( + GCRS(gcrs_rep, obstime=skycoord.obstime) + .transform_to(ITRS(obstime=skycoord.obstime)) + .earth_location + ) tete_frame = TETE(obstime=skycoord.obstime, location=location) return skycoord.transform_to(tete_frame) diff --git a/astropy/coordinates/spectral_coordinate.py b/astropy/coordinates/spectral_coordinate.py index 1b82f560a1d..c4234a3bdf1 100644 --- a/astropy/coordinates/spectral_coordinate.py +++ b/astropy/coordinates/spectral_coordinate.py @@ -15,7 +15,7 @@ from astropy.coordinates.spectral_quantity import SpectralQuantity from astropy.utils.exceptions import AstropyUserWarning -__all__ = ['SpectralCoord'] +__all__ = ["SpectralCoord"] class NoVelocityWarning(AstropyUserWarning): @@ -33,7 +33,7 @@ class NoDistanceWarning(AstropyUserWarning): DEFAULT_DISTANCE = 1e6 * u.kpc # We don't want to run doctests in the docstrings we inherit from Quantity -__doctest_skip__ = ['SpectralCoord.*'] +__doctest_skip__ = ["SpectralCoord.*"] def _apply_relativistic_doppler_shift(scoord, velocity): @@ -59,19 +59,24 @@ def _apply_relativistic_doppler_shift(scoord, velocity): if squantity.unit.is_equivalent(u.m): # wavelength return squantity * doppler_factor - elif (squantity.unit.is_equivalent(u.Hz) or - squantity.unit.is_equivalent(u.eV) or - squantity.unit.is_equivalent(1 / u.m)): + elif ( + squantity.unit.is_equivalent(u.Hz) + or squantity.unit.is_equivalent(u.eV) + or squantity.unit.is_equivalent(1 / u.m) + ): return squantity / doppler_factor elif squantity.unit.is_equivalent(KMS): # velocity return (squantity.to(u.Hz) / doppler_factor).to(squantity.unit) else: # pragma: no cover - raise RuntimeError(f"Unexpected units in velocity shift: {squantity.unit}. " - "This should not happen, so please report this in the " - "astropy issue tracker!") + raise RuntimeError( + f"Unexpected units in velocity shift: {squantity.unit}. This should not" + " happen, so please report this in the astropy issue tracker!" + ) -def update_differentials_to_match(original, velocity_reference, preserve_observer_frame=False): +def update_differentials_to_match( + original, velocity_reference, preserve_observer_frame=False +): """ Given an original coordinate object, update the differentials so that the final coordinate is at the same location as the original coordinate @@ -87,7 +92,9 @@ def update_differentials_to_match(original, velocity_reference, preserve_observe # If the reference has an obstime already defined, we should ignore # it and stick with the original observer obstime. - if 'obstime' in velocity_reference.frame_attributes and hasattr(original, 'obstime'): + if "obstime" in velocity_reference.frame_attributes and hasattr( + original, "obstime" + ): velocity_reference = velocity_reference.replicate(obstime=original.obstime) # We transform both coordinates to ICRS for simplicity and because we know @@ -97,11 +104,13 @@ def update_differentials_to_match(original, velocity_reference, preserve_observe original_icrs = original.transform_to(ICRS()) velocity_reference_icrs = velocity_reference.transform_to(ICRS()) - differentials = velocity_reference_icrs.data.represent_as(CartesianRepresentation, - CartesianDifferential).differentials + differentials = velocity_reference_icrs.data.represent_as( + CartesianRepresentation, CartesianDifferential + ).differentials - data_with_differentials = (original_icrs.data.represent_as(CartesianRepresentation) - .with_differentials(differentials)) + data_with_differentials = original_icrs.data.represent_as( + CartesianRepresentation + ).with_differentials(differentials) final_icrs = original_icrs.realize_frame(data_with_differentials) @@ -110,8 +119,10 @@ def update_differentials_to_match(original, velocity_reference, preserve_observe else: final = final_icrs.transform_to(velocity_reference) - return final.replicate(representation_type=CartesianRepresentation, - differential_type=CartesianDifferential) + return final.replicate( + representation_type=CartesianRepresentation, + differential_type=CartesianDifferential, + ) def attach_zero_velocities(coord): @@ -123,7 +134,7 @@ def attach_zero_velocities(coord): def _get_velocities(coord): - if 's' in coord.data.differentials: + if "s" in coord.data.differentials: return coord.velocity else: return ZERO_VELOCITIES @@ -170,12 +181,17 @@ class SpectralCoord(SpectralQuantity): The Doppler convention to use when expressing the spectral value as a velocity. """ - @u.quantity_input(radial_velocity=u.km/u.s) - def __new__(cls, value, unit=None, - observer=None, target=None, - radial_velocity=None, redshift=None, - **kwargs): - + @u.quantity_input(radial_velocity=u.km / u.s) + def __new__( + cls, + value, + unit=None, + observer=None, + target=None, + radial_velocity=None, + redshift=None, + **kwargs, + ): obj = super().__new__(cls, value, unit=unit, **kwargs) # There are two main modes of operation in this class. Either the @@ -187,8 +203,10 @@ def __new__(cls, value, unit=None, # or redshift. if target is not None and observer is not None: if radial_velocity is not None or redshift is not None: - raise ValueError("Cannot specify radial velocity or redshift if both " - "target and observer are specified") + raise ValueError( + "Cannot specify radial velocity or redshift if both " + "target and observer are specified" + ) # We only deal with redshifts here and in the redshift property. # Otherwise internally we always deal with velocities. @@ -201,37 +219,37 @@ def __new__(cls, value, unit=None, # example as in https://github.com/astropy/astropy/pull/10232, we # can remove the check here and add redshift=u.one to the decorator if not redshift.unit.is_equivalent(u.one): - raise u.UnitsError('redshift should be dimensionless') + raise u.UnitsError("redshift should be dimensionless") radial_velocity = redshift.to(u.km / u.s, u.doppler_redshift()) # If we're initializing from an existing SpectralCoord, keep any # parameters that aren't being overridden if observer is None: - observer = getattr(value, 'observer', None) + observer = getattr(value, "observer", None) if target is None: - target = getattr(value, 'target', None) + target = getattr(value, "target", None) # As mentioned above, we should only specify the radial velocity # manually if either or both the observer and target are not # specified. if observer is None or target is None: if radial_velocity is None: - radial_velocity = getattr(value, 'radial_velocity', None) + radial_velocity = getattr(value, "radial_velocity", None) obj._radial_velocity = radial_velocity - obj._observer = cls._validate_coordinate(observer, label='observer') - obj._target = cls._validate_coordinate(target, label='target') + obj._observer = cls._validate_coordinate(observer, label="observer") + obj._target = cls._validate_coordinate(target, label="target") return obj def __array_finalize__(self, obj): super().__array_finalize__(obj) - self._radial_velocity = getattr(obj, '_radial_velocity', None) - self._observer = getattr(obj, '_observer', None) - self._target = getattr(obj, '_target', None) + self._radial_velocity = getattr(obj, "_radial_velocity", None) + self._observer = getattr(obj, "_observer", None) + self._target = getattr(obj, "_target", None) @staticmethod - def _validate_coordinate(coord, label=''): + def _validate_coordinate(coord, label=""): """ Checks the type of the frame and whether a velocity differential and a distance has been defined on the frame object. @@ -255,39 +273,50 @@ def _validate_coordinate(coord, label=''): if isinstance(coord, SkyCoord): coord = coord.frame else: - raise TypeError(f"{label} must be a SkyCoord or coordinate frame instance") + raise TypeError( + f"{label} must be a SkyCoord or coordinate frame instance" + ) # If the distance is not well-defined, ensure that it works properly # for generating differentials # TODO: change this to not set the distance and yield a warning once # there's a good way to address this in astropy.coordinates # https://github.com/astropy/astropy/issues/10247 - with np.errstate(all='ignore'): - distance = getattr(coord, 'distance', None) - if distance is not None and distance.unit.physical_type == 'dimensionless': + with np.errstate(all="ignore"): + distance = getattr(coord, "distance", None) + if distance is not None and distance.unit.physical_type == "dimensionless": coord = SkyCoord(coord, distance=DEFAULT_DISTANCE) warnings.warn( "Distance on coordinate object is dimensionless, an " f"arbitrary distance value of {DEFAULT_DISTANCE} will be set instead.", - NoDistanceWarning) + NoDistanceWarning, + ) # If the observer frame does not contain information about the # velocity of the system, assume that the velocity is zero in the # system. - if 's' not in coord.data.differentials: + if "s" not in coord.data.differentials: warnings.warn( f"No velocity defined on frame, assuming {ZERO_VELOCITIES}.", - NoVelocityWarning) + NoVelocityWarning, + ) coord = attach_zero_velocities(coord) return coord - def replicate(self, value=None, unit=None, - observer=None, target=None, - radial_velocity=None, redshift=None, - doppler_convention=None, doppler_rest=None, - copy=False): + def replicate( + self, + value=None, + unit=None, + observer=None, + target=None, + radial_velocity=None, + redshift=None, + doppler_convention=None, + doppler_rest=None, + copy=False, + ): """ Return a replica of the `SpectralCoord`, optionally changing the values or attributes. @@ -331,7 +360,9 @@ def replicate(self, value=None, unit=None, if isinstance(value, u.Quantity): if unit is not None: - raise ValueError("Cannot specify value as a Quantity and also specify unit") + raise ValueError( + "Cannot specify value as a Quantity and also specify unit" + ) else: value, unit = value.value, value.unit @@ -348,15 +379,26 @@ def replicate(self, value=None, unit=None, # Only include radial_velocity if it is not auto-computed from the # observer and target. - if (self.observer is None or self.target is None) and radial_velocity is None and redshift is None: + if ( + (self.observer is None or self.target is None) + and radial_velocity is None + and redshift is None + ): radial_velocity = self.radial_velocity with warnings.catch_warnings(): - warnings.simplefilter('ignore', NoVelocityWarning) - return self.__class__(value=value, unit=unit, - observer=observer, target=target, - radial_velocity=radial_velocity, redshift=redshift, - doppler_convention=doppler_convention, doppler_rest=doppler_rest, copy=False) + warnings.simplefilter("ignore", NoVelocityWarning) + return self.__class__( + value=value, + unit=unit, + observer=observer, + target=target, + radial_velocity=radial_velocity, + redshift=redshift, + doppler_convention=doppler_convention, + doppler_rest=doppler_rest, + copy=False, + ) @property def quantity(self): @@ -389,11 +431,10 @@ def observer(self): @observer.setter def observer(self, value): - if self.observer is not None: raise ValueError("observer has already been set") - self._observer = self._validate_coordinate(value, label='observer') + self._observer = self._validate_coordinate(value, label="observer") # Switch to auto-computing radial velocity if self._target is not None: @@ -416,11 +457,10 @@ def target(self): @target.setter def target(self, value): - if self.target is not None: raise ValueError("target has already been set") - self._target = self._validate_coordinate(value, label='target') + self._target = self._validate_coordinate(value, label="target") # Switch to auto-computing radial velocity if self._observer is not None: @@ -448,8 +488,9 @@ def radial_velocity(self): else: return self._radial_velocity else: - return self._calculate_radial_velocity(self._observer, self._target, - as_scalar=True) + return self._calculate_radial_velocity( + self._observer, self._target, as_scalar=True + ) @property def redshift(self): @@ -518,8 +559,10 @@ def _normalized_position_vector(observer, target): pos_hat : `BaseRepresentation` Position representation. """ - d_pos = (target.cartesian.without_differentials() - - observer.cartesian.without_differentials()) + d_pos = ( + target.cartesian.without_differentials() + - observer.cartesian.without_differentials() + ) dp_norm = d_pos.norm() @@ -530,8 +573,10 @@ def _normalized_position_vector(observer, target): return pos_hat - @u.quantity_input(velocity=u.km/u.s) - def with_observer_stationary_relative_to(self, frame, velocity=None, preserve_observer_frame=False): + @u.quantity_input(velocity=u.km / u.s) + def with_observer_stationary_relative_to( + self, frame, velocity=None, preserve_observer_frame=False + ): """ A new `SpectralCoord` with the velocity of the observer altered, but not the position. @@ -566,28 +611,35 @@ def with_observer_stationary_relative_to(self, frame, velocity=None, preserve_ob """ if self.observer is None or self.target is None: - raise ValueError("This method can only be used if both observer " - "and target are defined on the SpectralCoord.") + raise ValueError( + "This method can only be used if both observer " + "and target are defined on the SpectralCoord." + ) # Start off by extracting frame if a SkyCoord was passed in if isinstance(frame, SkyCoord): frame = frame.frame if isinstance(frame, BaseCoordinateFrame): - if not frame.has_data: - frame = frame.realize_frame(CartesianRepresentation(0 * u.km, 0 * u.km, 0 * u.km)) + frame = frame.realize_frame( + CartesianRepresentation(0 * u.km, 0 * u.km, 0 * u.km) + ) if frame.data.differentials: if velocity is not None: - raise ValueError('frame already has differentials, cannot also specify velocity') + raise ValueError( + "frame already has differentials, cannot also specify velocity" + ) # otherwise frame is ready to go else: if velocity is None: differentials = ZERO_VELOCITIES else: differentials = CartesianDifferential(velocity) - frame = frame.realize_frame(frame.data.with_differentials(differentials)) + frame = frame.realize_frame( + frame.data.with_differentials(differentials) + ) if isinstance(frame, (type, str)): if isinstance(frame, type): @@ -597,18 +649,27 @@ def with_observer_stationary_relative_to(self, frame, velocity=None, preserve_ob if velocity is None: velocity = 0 * u.m / u.s, 0 * u.m / u.s, 0 * u.m / u.s elif velocity.shape != (3,): - raise ValueError('velocity should be a Quantity vector with 3 elements') - frame = frame_cls(0 * u.m, 0 * u.m, 0 * u.m, - *velocity, - representation_type='cartesian', - differential_type='cartesian') - - observer = update_differentials_to_match(self.observer, frame, - preserve_observer_frame=preserve_observer_frame) + raise ValueError("velocity should be a Quantity vector with 3 elements") + frame = frame_cls( + 0 * u.m, + 0 * u.m, + 0 * u.m, + *velocity, + representation_type="cartesian", + differential_type="cartesian", + ) + + observer = update_differentials_to_match( + self.observer, frame, preserve_observer_frame=preserve_observer_frame + ) # Calculate the initial and final los velocity - init_obs_vel = self._calculate_radial_velocity(self.observer, self.target, as_scalar=True) - fin_obs_vel = self._calculate_radial_velocity(observer, self.target, as_scalar=True) + init_obs_vel = self._calculate_radial_velocity( + self.observer, self.target, as_scalar=True + ) + fin_obs_vel = self._calculate_radial_velocity( + observer, self.target, as_scalar=True + ) # Apply transformation to data new_data = _apply_relativistic_doppler_shift(self, fin_obs_vel - init_obs_vel) @@ -639,16 +700,20 @@ def with_radial_velocity_shift(self, target_shift=None, observer_shift=None): ``target_shift`` and ``observer_shift`` are both `None`. """ - if observer_shift is not None and (self.target is None or - self.observer is None): - raise ValueError("Both an observer and target must be defined " - "before applying a velocity shift.") + if observer_shift is not None and ( + self.target is None or self.observer is None + ): + raise ValueError( + "Both an observer and target must be defined " + "before applying a velocity shift." + ) for arg in [x for x in [target_shift, observer_shift] if x is not None]: if isinstance(arg, u.Quantity) and not arg.unit.is_equivalent((u.one, KMS)): - raise u.UnitsError("Argument must have unit physical type " - "'speed' for radial velocty or " - "'dimensionless' for redshift.") + raise u.UnitsError( + "Argument must have unit physical type 'speed' for radial velocty" + " or 'dimensionless' for redshift." + ) # The target or observer value is defined but is not a quantity object, # assume it's a redshift float value and convert to velocity @@ -659,17 +724,19 @@ def with_radial_velocity_shift(self, target_shift=None, observer_shift=None): target_shift = 0 * KMS else: target_shift = u.Quantity(target_shift) - if target_shift.unit.physical_type == 'dimensionless': + if target_shift.unit.physical_type == "dimensionless": target_shift = target_shift.to(u.km / u.s, u.doppler_redshift()) if self._observer is None or self._target is None: - return self.replicate(value=_apply_relativistic_doppler_shift(self, target_shift), - radial_velocity=self.radial_velocity + target_shift) + return self.replicate( + value=_apply_relativistic_doppler_shift(self, target_shift), + radial_velocity=self.radial_velocity + target_shift, + ) if observer_shift is None: observer_shift = 0 * KMS else: observer_shift = u.Quantity(observer_shift) - if observer_shift.unit.physical_type == 'dimensionless': + if observer_shift.unit.physical_type == "dimensionless": observer_shift = observer_shift.to(u.km / u.s, u.doppler_redshift()) target_icrs = self._target.transform_to(ICRS()) @@ -683,22 +750,24 @@ def with_radial_velocity_shift(self, target_shift=None, observer_shift=None): target_velocity = CartesianDifferential(target_velocity.xyz) observer_velocity = CartesianDifferential(observer_velocity.xyz) - new_target = (target_icrs - .realize_frame(target_icrs.cartesian.with_differentials(target_velocity)) - .transform_to(self._target)) + new_target = target_icrs.realize_frame( + target_icrs.cartesian.with_differentials(target_velocity) + ).transform_to(self._target) - new_observer = (observer_icrs - .realize_frame(observer_icrs.cartesian.with_differentials(observer_velocity)) - .transform_to(self._observer)) + new_observer = observer_icrs.realize_frame( + observer_icrs.cartesian.with_differentials(observer_velocity) + ).transform_to(self._observer) - init_obs_vel = self._calculate_radial_velocity(observer_icrs, target_icrs, as_scalar=True) - fin_obs_vel = self._calculate_radial_velocity(new_observer, new_target, as_scalar=True) + init_obs_vel = self._calculate_radial_velocity( + observer_icrs, target_icrs, as_scalar=True + ) + fin_obs_vel = self._calculate_radial_velocity( + new_observer, new_target, as_scalar=True + ) new_data = _apply_relativistic_doppler_shift(self, fin_obs_vel - init_obs_vel) - return self.replicate(value=new_data, - observer=new_observer, - target=new_target) + return self.replicate(value=new_data, observer=new_observer, target=new_target) def to_rest(self): """ @@ -710,48 +779,48 @@ def to_rest(self): result = _apply_relativistic_doppler_shift(self, -self.radial_velocity) - return self.replicate(value=result, radial_velocity=0. * KMS, redshift=None) + return self.replicate(value=result, radial_velocity=0.0 * KMS, redshift=None) def __repr__(self): - - prefixstr = '<' + self.__class__.__name__ + ' ' + prefixstr = "<" + self.__class__.__name__ + " " try: radial_velocity = self.radial_velocity redshift = self.redshift except ValueError: - radial_velocity = redshift = 'Undefined' + radial_velocity = redshift = "Undefined" - repr_items = [f'{prefixstr}'] + repr_items = [f"{prefixstr}"] if self.observer is not None: - observer_repr = indent(repr(self.observer), 14 * ' ').lstrip() - repr_items.append(f' observer: {observer_repr}') + observer_repr = indent(repr(self.observer), 14 * " ").lstrip() + repr_items.append(f" observer: {observer_repr}") if self.target is not None: - target_repr = indent(repr(self.target), 12 * ' ').lstrip() - repr_items.append(f' target: {target_repr}') + target_repr = indent(repr(self.target), 12 * " ").lstrip() + repr_items.append(f" target: {target_repr}") - if (self._observer is not None and self._target is not None) or self._radial_velocity is not None: + if ( + self._observer is not None and self._target is not None + ) or self._radial_velocity is not None: if self.observer is not None and self.target is not None: - repr_items.append(' observer to target (computed from above):') + repr_items.append(" observer to target (computed from above):") else: - repr_items.append(' observer to target:') - repr_items.append(f' radial_velocity={radial_velocity}') - repr_items.append(f' redshift={redshift}') + repr_items.append(" observer to target:") + repr_items.append(f" radial_velocity={radial_velocity}") + repr_items.append(f" redshift={redshift}") if self.doppler_rest is not None or self.doppler_convention is not None: - repr_items.append(f' doppler_rest={self.doppler_rest}') - repr_items.append(f' doppler_convention={self.doppler_convention}') + repr_items.append(f" doppler_rest={self.doppler_rest}") + repr_items.append(f" doppler_convention={self.doppler_convention}") - arrstr = np.array2string(self.view(np.ndarray), separator=', ', - prefix=' ') + arrstr = np.array2string(self.view(np.ndarray), separator=", ", prefix=" ") if len(repr_items) == 1: - repr_items[0] += f'{arrstr}{self._unitstr:s}' + repr_items[0] += f"{arrstr}{self._unitstr:s}" else: - repr_items[1] = ' (' + repr_items[1].lstrip() - repr_items[-1] += ')' - repr_items.append(f' {arrstr}{self._unitstr:s}') + repr_items[1] = " (" + repr_items[1].lstrip() + repr_items[-1] += ")" + repr_items.append(f" {arrstr}{self._unitstr:s}") - return '\n'.join(repr_items) + '>' + return "\n".join(repr_items) + ">" diff --git a/astropy/coordinates/spectral_quantity.py b/astropy/coordinates/spectral_quantity.py index 9ebb436f767..f94147f2ef9 100644 --- a/astropy/coordinates/spectral_quantity.py +++ b/astropy/coordinates/spectral_quantity.py @@ -6,19 +6,19 @@ from astropy.units.decorators import quantity_input from astropy.units.quantity import Quantity, SpecificTypeQuantity -__all__ = ['SpectralQuantity'] +__all__ = ["SpectralQuantity"] # We don't want to run doctests in the docstrings we inherit from Quantity -__doctest_skip__ = ['SpectralQuantity.*'] +__doctest_skip__ = ["SpectralQuantity.*"] KMS = si.km / si.s -SPECTRAL_UNITS = (si.Hz, si.m, si.J, si.m ** -1, KMS) +SPECTRAL_UNITS = (si.Hz, si.m, si.J, si.m**-1, KMS) DOPPLER_CONVENTIONS = { - 'radio': eq.doppler_radio, - 'optical': eq.doppler_optical, - 'relativistic': eq.doppler_relativistic + "radio": eq.doppler_radio, + "optical": eq.doppler_optical, + "relativistic": eq.doppler_relativistic, } @@ -51,18 +51,17 @@ class SpectralQuantity(SpecificTypeQuantity): _include_easy_conversion_members = True - def __new__(cls, value, unit=None, - doppler_rest=None, doppler_convention=None, - **kwargs): - + def __new__( + cls, value, unit=None, doppler_rest=None, doppler_convention=None, **kwargs + ): obj = super().__new__(cls, value, unit=unit, **kwargs) # If we're initializing from an existing SpectralQuantity, keep any # parameters that aren't being overridden if doppler_rest is None: - doppler_rest = getattr(value, 'doppler_rest', None) + doppler_rest = getattr(value, "doppler_rest", None) if doppler_convention is None: - doppler_convention = getattr(value, 'doppler_convention', None) + doppler_convention = getattr(value, "doppler_convention", None) obj._doppler_rest = doppler_rest obj._doppler_convention = doppler_convention @@ -71,8 +70,8 @@ def __new__(cls, value, unit=None, def __array_finalize__(self, obj): super().__array_finalize__(obj) - self._doppler_rest = getattr(obj, '_doppler_rest', None) - self._doppler_convention = getattr(obj, '_doppler_convention', None) + self._doppler_rest = getattr(obj, "_doppler_rest", None) + self._doppler_convention = getattr(obj, "_doppler_convention", None) def __quantity_subclass__(self, unit): # Always default to just returning a Quantity, unless we explicitly @@ -87,17 +86,27 @@ def __quantity_subclass__(self, unit): def __array_ufunc__(self, function, method, *inputs, **kwargs): # We always return Quantity except in a few specific cases result = super().__array_ufunc__(function, method, *inputs, **kwargs) - if ((function is np.multiply - or function is np.true_divide and inputs[0] is self) + if ( + ( + function is np.multiply + or function is np.true_divide + and inputs[0] is self + ) and result.unit == self.unit - or (function in (np.minimum, np.maximum, np.fmax, np.fmin) - and method in ('reduce', 'reduceat'))): + or ( + function in (np.minimum, np.maximum, np.fmax, np.fmin) + and method in ("reduce", "reduceat") + ) + ): result = result.view(self.__class__) result.__array_finalize__(self) else: if result is self: - raise TypeError(f"Cannot store the result of this operation in {self.__class__.__name__}") - if result.dtype.kind == 'b': + raise TypeError( + "Cannot store the result of this operation in" + f" {self.__class__.__name__}" + ) + if result.dtype.kind == "b": result = result.view(np.ndarray) else: result = result.view(Quantity) @@ -128,10 +137,11 @@ def doppler_rest(self, value): Rest value. """ if self._doppler_rest is not None: - raise AttributeError("doppler_rest has already been set, and cannot " - "be changed. Use the ``to`` method to convert " - "the spectral values(s) to use a different " - "rest value") + raise AttributeError( + "doppler_rest has already been set, and cannot be changed. Use the" + " ``to`` method to convert the spectral values(s) to use a different" + " rest value" + ) self._doppler_rest = value @property @@ -168,21 +178,22 @@ def doppler_convention(self, value): """ if self._doppler_convention is not None: - raise AttributeError("doppler_convention has already been set, and cannot " - "be changed. Use the ``to`` method to convert " - "the spectral values(s) to use a different " - "convention") + raise AttributeError( + "doppler_convention has already been set, and cannot be changed. Use" + " the ``to`` method to convert the spectral values(s) to use a" + " different convention" + ) if value is not None and value not in DOPPLER_CONVENTIONS: - raise ValueError(f"doppler_convention should be one of {'/'.join(sorted(DOPPLER_CONVENTIONS))}") + raise ValueError( + "doppler_convention should be one of" + f" {'/'.join(sorted(DOPPLER_CONVENTIONS))}" + ) self._doppler_convention = value @quantity_input(doppler_rest=SPECTRAL_UNITS) - def to(self, unit, - equivalencies=[], - doppler_rest=None, - doppler_convention=None): + def to(self, unit, equivalencies=[], doppler_rest=None, doppler_convention=None): """ Return a new `~astropy.coordinates.SpectralQuantity` object with the specified unit. @@ -237,10 +248,12 @@ def to(self, unit, if doppler_convention is None: doppler_convention = self._doppler_convention elif doppler_convention not in DOPPLER_CONVENTIONS: - raise ValueError(f"doppler_convention should be one of {'/'.join(sorted(DOPPLER_CONVENTIONS))}") + raise ValueError( + "doppler_convention should be one of" + f" {'/'.join(sorted(DOPPLER_CONVENTIONS))}" + ) if self.unit.is_equivalent(KMS) and unit.is_equivalent(KMS): - # Special case: if the current and final units are both velocity, # and either the rest value or the convention are different, we # need to convert back to frequency temporarily. @@ -258,11 +271,14 @@ def to(self, unit, return result elif (doppler_rest is None) is not (doppler_convention is None): - raise ValueError("Either both or neither doppler_rest and " - "doppler_convention should be defined for " - "velocity conversions") + raise ValueError( + "Either both or neither doppler_rest and doppler_convention should" + " be defined for velocity conversions" + ) - vel_equiv1 = DOPPLER_CONVENTIONS[self._doppler_convention](self._doppler_rest) + vel_equiv1 = DOPPLER_CONVENTIONS[self._doppler_convention]( + self._doppler_rest + ) freq = super().to(si.Hz, equivalencies=equivalencies + vel_equiv1) @@ -271,20 +287,27 @@ def to(self, unit, result = freq.to(unit, equivalencies=equivalencies + vel_equiv2) else: - additional_equivalencies = eq.spectral() if self.unit.is_equivalent(KMS) or unit.is_equivalent(KMS): - if doppler_convention is None: - raise ValueError("doppler_convention not set, cannot convert to/from velocities") + raise ValueError( + "doppler_convention not set, cannot convert to/from velocities" + ) if doppler_rest is None: - raise ValueError("doppler_rest not set, cannot convert to/from velocities") - - additional_equivalencies = additional_equivalencies + DOPPLER_CONVENTIONS[doppler_convention](doppler_rest) - - result = super().to(unit, equivalencies=equivalencies + additional_equivalencies) + raise ValueError( + "doppler_rest not set, cannot convert to/from velocities" + ) + + additional_equivalencies = ( + additional_equivalencies + + DOPPLER_CONVENTIONS[doppler_convention](doppler_rest) + ) + + result = super().to( + unit, equivalencies=equivalencies + additional_equivalencies + ) # Since we have to explicitly specify when we want to keep this as a # SpectralQuantity, we need to convert it back from a Quantity to diff --git a/astropy/coordinates/tests/accuracy/generate_ref_ast.py b/astropy/coordinates/tests/accuracy/generate_ref_ast.py index 638fe24220c..90509876856 100644 --- a/astropy/coordinates/tests/accuracy/generate_ref_ast.py +++ b/astropy/coordinates/tests/accuracy/generate_ref_ast.py @@ -11,7 +11,7 @@ from astropy.table import Column, Table -def ref_fk4_no_e_fk4(fnout='fk4_no_e_fk4.csv'): +def ref_fk4_no_e_fk4(fnout="fk4_no_e_fk4.csv"): """ Accuracy tests for the FK4 (with no E-terms of aberration) to/from FK4 conversion, with arbitrary equinoxes and epoch of observation. @@ -26,20 +26,19 @@ def ref_fk4_no_e_fk4(fnout='fk4_no_e_fk4.csv'): # Sample uniformly on the unit sphere. These will be either the FK4 # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to FK4. - ra = np.random.uniform(0., 360., N) - dec = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) + ra = np.random.uniform(0.0, 360.0, N) + dec = np.degrees(np.arcsin(np.random.uniform(-1.0, 1.0, N))) # Generate random observation epoch and equinoxes - obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] + obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950.0, 2000.0, N)] ra_fk4ne, dec_fk4ne = [], [] ra_fk4, dec_fk4 = [], [] for i in range(N): - # Set up frames for AST - frame_fk4ne = Ast.SkyFrame(f'System=FK4-NO-E,Epoch={obstime[i]},Equinox=B1950') - frame_fk4 = Ast.SkyFrame(f'System=FK4,Epoch={obstime[i]},Equinox=B1950') + frame_fk4ne = Ast.SkyFrame(f"System=FK4-NO-E,Epoch={obstime[i]},Equinox=B1950") + frame_fk4 = Ast.SkyFrame(f"System=FK4,Epoch={obstime[i]},Equinox=B1950") # FK4 to FK4 (no E-terms) frameset = frame_fk4.convert(frame_fk4ne) @@ -55,20 +54,22 @@ def ref_fk4_no_e_fk4(fnout='fk4_no_e_fk4.csv'): # Write out table to a CSV file t = Table() - t.add_column(Column(name='obstime', data=obstime)) - t.add_column(Column(name='ra_in', data=ra)) - t.add_column(Column(name='dec_in', data=dec)) - t.add_column(Column(name='ra_fk4ne', data=ra_fk4ne)) - t.add_column(Column(name='dec_fk4ne', data=dec_fk4ne)) - t.add_column(Column(name='ra_fk4', data=ra_fk4)) - t.add_column(Column(name='dec_fk4', data=dec_fk4)) - f = open(os.path.join('data', fnout), 'wb') - f.write("# This file was generated with the {} script, and the reference " - "values were computed using AST\n".format(os.path.basename(__file__))) - t.write(f, format='ascii', delimiter=',') - - -def ref_fk4_no_e_fk5(fnout='fk4_no_e_fk5.csv'): + t.add_column(Column(name="obstime", data=obstime)) + t.add_column(Column(name="ra_in", data=ra)) + t.add_column(Column(name="dec_in", data=dec)) + t.add_column(Column(name="ra_fk4ne", data=ra_fk4ne)) + t.add_column(Column(name="dec_fk4ne", data=dec_fk4ne)) + t.add_column(Column(name="ra_fk4", data=ra_fk4)) + t.add_column(Column(name="dec_fk4", data=dec_fk4)) + f = open(os.path.join("data", fnout), "wb") + f.write( + f"# This file was generated with the {os.path.basename(__file__)} script, and" + " the reference values were computed using AST\n" + ) + t.write(f, format="ascii", delimiter=",") + + +def ref_fk4_no_e_fk5(fnout="fk4_no_e_fk5.csv"): """ Accuracy tests for the FK4 (with no E-terms of aberration) to/from FK5 conversion, with arbitrary equinoxes and epoch of observation. @@ -83,22 +84,25 @@ def ref_fk4_no_e_fk5(fnout='fk4_no_e_fk5.csv'): # Sample uniformly on the unit sphere. These will be either the FK4 # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to FK4. - ra = np.random.uniform(0., 360., N) - dec = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) + ra = np.random.uniform(0.0, 360.0, N) + dec = np.degrees(np.arcsin(np.random.uniform(-1.0, 1.0, N))) # Generate random observation epoch and equinoxes - obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] - equinox_fk4 = [f"B{x:7.2f}" for x in np.random.uniform(1925., 1975., N)] - equinox_fk5 = [f"J{x:7.2f}" for x in np.random.uniform(1975., 2025., N)] + obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950.0, 2000.0, N)] + equinox_fk4 = [f"B{x:7.2f}" for x in np.random.uniform(1925.0, 1975.0, N)] + equinox_fk5 = [f"J{x:7.2f}" for x in np.random.uniform(1975.0, 2025.0, N)] ra_fk4, dec_fk4 = [], [] ra_fk5, dec_fk5 = [], [] for i in range(N): - # Set up frames for AST - frame_fk4 = Ast.SkyFrame(f'System=FK4-NO-E,Epoch={obstime[i]},Equinox={equinox_fk4[i]}') - frame_fk5 = Ast.SkyFrame(f'System=FK5,Epoch={obstime[i]},Equinox={equinox_fk5[i]}') + frame_fk4 = Ast.SkyFrame( + f"System=FK4-NO-E,Epoch={obstime[i]},Equinox={equinox_fk4[i]}" + ) + frame_fk5 = Ast.SkyFrame( + f"System=FK5,Epoch={obstime[i]},Equinox={equinox_fk5[i]}" + ) # FK4 to FK5 frameset = frame_fk4.convert(frame_fk5) @@ -114,22 +118,24 @@ def ref_fk4_no_e_fk5(fnout='fk4_no_e_fk5.csv'): # Write out table to a CSV file t = Table() - t.add_column(Column(name='equinox_fk4', data=equinox_fk4)) - t.add_column(Column(name='equinox_fk5', data=equinox_fk5)) - t.add_column(Column(name='obstime', data=obstime)) - t.add_column(Column(name='ra_in', data=ra)) - t.add_column(Column(name='dec_in', data=dec)) - t.add_column(Column(name='ra_fk5', data=ra_fk5)) - t.add_column(Column(name='dec_fk5', data=dec_fk5)) - t.add_column(Column(name='ra_fk4', data=ra_fk4)) - t.add_column(Column(name='dec_fk4', data=dec_fk4)) - f = open(os.path.join('data', fnout), 'wb') - f.write("# This file was generated with the {} script, and the reference " - "values were computed using AST\n".format(os.path.basename(__file__))) - t.write(f, format='ascii', delimiter=',') - - -def ref_galactic_fk4(fnout='galactic_fk4.csv'): + t.add_column(Column(name="equinox_fk4", data=equinox_fk4)) + t.add_column(Column(name="equinox_fk5", data=equinox_fk5)) + t.add_column(Column(name="obstime", data=obstime)) + t.add_column(Column(name="ra_in", data=ra)) + t.add_column(Column(name="dec_in", data=dec)) + t.add_column(Column(name="ra_fk5", data=ra_fk5)) + t.add_column(Column(name="dec_fk5", data=dec_fk5)) + t.add_column(Column(name="ra_fk4", data=ra_fk4)) + t.add_column(Column(name="dec_fk4", data=dec_fk4)) + f = open(os.path.join("data", fnout), "wb") + f.write( + f"# This file was generated with the {os.path.basename(__file__)} script, and" + " the reference values were computed using AST\n" + ) + t.write(f, format="ascii", delimiter=",") + + +def ref_galactic_fk4(fnout="galactic_fk4.csv"): """ Accuracy tests for the ICRS (with no E-terms of aberration) to/from FK5 conversion, with arbitrary equinoxes and epoch of observation. @@ -144,21 +150,22 @@ def ref_galactic_fk4(fnout='galactic_fk4.csv'): # Sample uniformly on the unit sphere. These will be either the ICRS # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to ICRS. - lon = np.random.uniform(0., 360., N) - lat = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) + lon = np.random.uniform(0.0, 360.0, N) + lat = np.degrees(np.arcsin(np.random.uniform(-1.0, 1.0, N))) # Generate random observation epoch and equinoxes - obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] - equinox_fk4 = [f"J{x:7.2f}" for x in np.random.uniform(1975., 2025., N)] + obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950.0, 2000.0, N)] + equinox_fk4 = [f"J{x:7.2f}" for x in np.random.uniform(1975.0, 2025.0, N)] lon_gal, lat_gal = [], [] ra_fk4, dec_fk4 = [], [] for i in range(N): - # Set up frames for AST - frame_gal = Ast.SkyFrame(f'System=Galactic,Epoch={obstime[i]}') - frame_fk4 = Ast.SkyFrame(f'System=FK4,Epoch={obstime[i]},Equinox={equinox_fk4[i]}') + frame_gal = Ast.SkyFrame(f"System=Galactic,Epoch={obstime[i]}") + frame_fk4 = Ast.SkyFrame( + f"System=FK4,Epoch={obstime[i]},Equinox={equinox_fk4[i]}" + ) # ICRS to FK5 frameset = frame_gal.convert(frame_fk4) @@ -174,21 +181,23 @@ def ref_galactic_fk4(fnout='galactic_fk4.csv'): # Write out table to a CSV file t = Table() - t.add_column(Column(name='equinox_fk4', data=equinox_fk4)) - t.add_column(Column(name='obstime', data=obstime)) - t.add_column(Column(name='lon_in', data=lon)) - t.add_column(Column(name='lat_in', data=lat)) - t.add_column(Column(name='ra_fk4', data=ra_fk4)) - t.add_column(Column(name='dec_fk4', data=dec_fk4)) - t.add_column(Column(name='lon_gal', data=lon_gal)) - t.add_column(Column(name='lat_gal', data=lat_gal)) - f = open(os.path.join('data', fnout), 'wb') - f.write("# This file was generated with the {} script, and the reference " - "values were computed using AST\n".format(os.path.basename(__file__))) - t.write(f, format='ascii', delimiter=',') - - -def ref_icrs_fk5(fnout='icrs_fk5.csv'): + t.add_column(Column(name="equinox_fk4", data=equinox_fk4)) + t.add_column(Column(name="obstime", data=obstime)) + t.add_column(Column(name="lon_in", data=lon)) + t.add_column(Column(name="lat_in", data=lat)) + t.add_column(Column(name="ra_fk4", data=ra_fk4)) + t.add_column(Column(name="dec_fk4", data=dec_fk4)) + t.add_column(Column(name="lon_gal", data=lon_gal)) + t.add_column(Column(name="lat_gal", data=lat_gal)) + f = open(os.path.join("data", fnout), "wb") + f.write( + f"# This file was generated with the {os.path.basename(__file__)} script, and" + " the reference values were computed using AST\n" + ) + t.write(f, format="ascii", delimiter=",") + + +def ref_icrs_fk5(fnout="icrs_fk5.csv"): """ Accuracy tests for the ICRS (with no E-terms of aberration) to/from FK5 conversion, with arbitrary equinoxes and epoch of observation. @@ -203,21 +212,22 @@ def ref_icrs_fk5(fnout='icrs_fk5.csv'): # Sample uniformly on the unit sphere. These will be either the ICRS # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to ICRS. - ra = np.random.uniform(0., 360., N) - dec = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) + ra = np.random.uniform(0.0, 360.0, N) + dec = np.degrees(np.arcsin(np.random.uniform(-1.0, 1.0, N))) # Generate random observation epoch and equinoxes - obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] - equinox_fk5 = [f"J{x:7.2f}" for x in np.random.uniform(1975., 2025., N)] + obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950.0, 2000.0, N)] + equinox_fk5 = [f"J{x:7.2f}" for x in np.random.uniform(1975.0, 2025.0, N)] ra_icrs, dec_icrs = [], [] ra_fk5, dec_fk5 = [], [] for i in range(N): - # Set up frames for AST - frame_icrs = Ast.SkyFrame(f'System=ICRS,Epoch={obstime[i]}') - frame_fk5 = Ast.SkyFrame(f'System=FK5,Epoch={obstime[i]},Equinox={equinox_fk5[i]}') + frame_icrs = Ast.SkyFrame(f"System=ICRS,Epoch={obstime[i]}") + frame_fk5 = Ast.SkyFrame( + f"System=FK5,Epoch={obstime[i]},Equinox={equinox_fk5[i]}" + ) # ICRS to FK5 frameset = frame_icrs.convert(frame_fk5) @@ -233,21 +243,23 @@ def ref_icrs_fk5(fnout='icrs_fk5.csv'): # Write out table to a CSV file t = Table() - t.add_column(Column(name='equinox_fk5', data=equinox_fk5)) - t.add_column(Column(name='obstime', data=obstime)) - t.add_column(Column(name='ra_in', data=ra)) - t.add_column(Column(name='dec_in', data=dec)) - t.add_column(Column(name='ra_fk5', data=ra_fk5)) - t.add_column(Column(name='dec_fk5', data=dec_fk5)) - t.add_column(Column(name='ra_icrs', data=ra_icrs)) - t.add_column(Column(name='dec_icrs', data=dec_icrs)) - f = open(os.path.join('data', fnout), 'wb') - f.write("# This file was generated with the {} script, and the reference " - "values were computed using AST\n".format(os.path.basename(__file__))) - t.write(f, format='ascii', delimiter=',') - - -if __name__ == '__main__': + t.add_column(Column(name="equinox_fk5", data=equinox_fk5)) + t.add_column(Column(name="obstime", data=obstime)) + t.add_column(Column(name="ra_in", data=ra)) + t.add_column(Column(name="dec_in", data=dec)) + t.add_column(Column(name="ra_fk5", data=ra_fk5)) + t.add_column(Column(name="dec_fk5", data=dec_fk5)) + t.add_column(Column(name="ra_icrs", data=ra_icrs)) + t.add_column(Column(name="dec_icrs", data=dec_icrs)) + f = open(os.path.join("data", fnout), "wb") + f.write( + f"# This file was generated with the {os.path.basename(__file__)} script, and" + " the reference values were computed using AST\n" + ) + t.write(f, format="ascii", delimiter=",") + + +if __name__ == "__main__": ref_fk4_no_e_fk4() ref_fk4_no_e_fk5() ref_galactic_fk4() diff --git a/astropy/coordinates/tests/accuracy/generate_spectralcoord_ref.py b/astropy/coordinates/tests/accuracy/generate_spectralcoord_ref.py index 2a3e50ea955..dd6c647a1e9 100644 --- a/astropy/coordinates/tests/accuracy/generate_spectralcoord_ref.py +++ b/astropy/coordinates/tests/accuracy/generate_spectralcoord_ref.py @@ -5,7 +5,6 @@ # at http://starlink.eao.hawaii.edu/starlink if __name__ == "__main__": - from random import choice from subprocess import check_output @@ -23,36 +22,45 @@ tab = QTable() target_lon = np.random.uniform(0, 360, N) * u.deg target_lat = np.degrees(np.arcsin(np.random.uniform(-1, 1, N))) * u.deg - tab['target'] = SkyCoord(target_lon, target_lat, frame='fk5') - tab['obstime'] = Time(np.random.uniform(Time('1997-01-01').mjd, Time('2017-12-31').mjd, N), format='mjd', scale='utc') - tab['obslon'] = Angle(np.random.uniform(-180, 180, N) * u.deg) - tab['obslat'] = Angle(np.arcsin(np.random.uniform(-1, 1, N)) * u.deg) - tab['geocent'] = 0. - tab['heliocent'] = 0. - tab['lsrk'] = 0. - tab['lsrd'] = 0. - tab['galactoc'] = 0. - tab['localgrp'] = 0. + tab["target"] = SkyCoord(target_lon, target_lat, frame="fk5") + tab["obstime"] = Time( + np.random.uniform(Time("1997-01-01").mjd, Time("2017-12-31").mjd, N), + format="mjd", + scale="utc", + ) + tab["obslon"] = Angle(np.random.uniform(-180, 180, N) * u.deg) + tab["obslat"] = Angle(np.arcsin(np.random.uniform(-1, 1, N)) * u.deg) + tab["geocent"] = 0.0 + tab["heliocent"] = 0.0 + tab["lsrk"] = 0.0 + tab["lsrd"] = 0.0 + tab["galactoc"] = 0.0 + tab["localgrp"] = 0.0 for row in tab: - # Produce input file for rv command - with open('rv.input', 'w') as f: - f.write(row['obslon'].to_string('deg', sep=' ') + ' ' + row['obslat'].to_string('deg', sep=' ') + '\n') - f.write(f"{row['obstime'].datetime.year} {row['obstime'].datetime.month} {row['obstime'].datetime.day} 1\n") - f.write(row['target'].to_string('hmsdms', sep=' ') + ' J2000\n') - f.write('END\n') + with open("rv.input", "w") as f: + f.write( + f"{row['obslon'].to_string('deg', sep=' ')}" + f" {row['obslat'].to_string('deg', sep=' ')}\n" + ) + f.write( + f"{row['obstime'].datetime.year} {row['obstime'].datetime.month}" + f" {row['obstime'].datetime.day} 1\n" + ) + f.write(row["target"].to_string("hmsdms", sep=" ") + " J2000\n") + f.write("END\n") # Run Starlink rv command - check_output(['rv', 'rv.input']) + check_output(["rv", "rv.input"]) # Parse values from output file lis_lines = [] started = False - for lis_line in open('rv.lis'): - if started and lis_line.strip() != '': + for lis_line in open("rv.lis"): + if started and lis_line.strip() != "": lis_lines.append(lis_line.strip()) - elif 'LOCAL GROUP' in lis_line: + elif "LOCAL GROUP" in lis_line: started = True # Some sources are not observable at the specified time and therefore don't @@ -68,11 +76,25 @@ # The column for 'SUN' has an entry also for the light travel time, which # we want to ignore. It sometimes includes '(' followed by a space which # can cause issues with splitting, hence why we get rid of the space. - lis_line = lis_line.replace('( ', '(').replace('( ', '(') - year, month, day, time, zd, row['geocent'], row['heliocent'], _, \ - row['lsrk'], row['lsrd'], row['galactoc'], row['localgrp'] = lis_line.split() - row['obstime'] = Time(f'{year}-{month}-{day}T{time}:00', format='isot', scale='utc') + lis_line = lis_line.replace("( ", "(").replace("( ", "(") + ( + year, + month, + day, + time, + zd, + row["geocent"], + row["heliocent"], + _, + row["lsrk"], + row["lsrd"], + row["galactoc"], + row["localgrp"], + ) = lis_line.split() + row["obstime"] = Time( + f"{year}-{month}-{day}T{time}:00", format="isot", scale="utc" + ) # We sampled 100 coordinates above since some may not have results - we now # truncate to 50 sources since this is sufficient. - tab[:50].write('reference_rv.ecsv', format='ascii.ecsv') + tab[:50].write("reference_rv.ecsv", format="ascii.ecsv") diff --git a/astropy/coordinates/tests/accuracy/test_altaz_icrs.py b/astropy/coordinates/tests/accuracy/test_altaz_icrs.py index 7ac0b0bed45..79e2e6374cc 100644 --- a/astropy/coordinates/tests/accuracy/test_altaz_icrs.py +++ b/astropy/coordinates/tests/accuracy/test_altaz_icrs.py @@ -52,30 +52,38 @@ def test_against_hor2eq(): """ # Observatory position for `kpno` from here: # http://idlastro.gsfc.nasa.gov/ftp/pro/astro/observatory.pro - location = EarthLocation(lon=Angle('-111d36.0m'), - lat=Angle('31d57.8m'), - height=2120. * u.m) - - obstime = Time(2451545.0, format='jd', scale='ut1') - - altaz_frame = AltAz(obstime=obstime, location=location, - temperature=0 * u.deg_C, pressure=0.781 * u.bar) - altaz_frame_noatm = AltAz(obstime=obstime, location=location, - temperature=0 * u.deg_C, pressure=0.0 * u.bar) - altaz = SkyCoord('264d55m06s 37d54m41s', frame=altaz_frame) - altaz_noatm = SkyCoord('264d55m06s 37d54m41s', frame=altaz_frame_noatm) - - radec_frame = 'icrs' + location = EarthLocation( + lon=Angle("-111d36.0m"), lat=Angle("31d57.8m"), height=2120.0 * u.m + ) + + obstime = Time(2451545.0, format="jd", scale="ut1") + + altaz_frame = AltAz( + obstime=obstime, + location=location, + temperature=0 * u.deg_C, + pressure=0.781 * u.bar, + ) + altaz_frame_noatm = AltAz( + obstime=obstime, + location=location, + temperature=0 * u.deg_C, + pressure=0.0 * u.bar, + ) + altaz = SkyCoord("264d55m06s 37d54m41s", frame=altaz_frame) + altaz_noatm = SkyCoord("264d55m06s 37d54m41s", frame=altaz_frame_noatm) + + radec_frame = "icrs" radec_actual = altaz.transform_to(radec_frame) radec_actual_noatm = altaz_noatm.transform_to(radec_frame) - radec_expected = SkyCoord('07h36m55.2s +15d25m08s', frame=radec_frame) - distance = radec_actual.separation(radec_expected).to('arcsec') + radec_expected = SkyCoord("07h36m55.2s +15d25m08s", frame=radec_frame) + distance = radec_actual.separation(radec_expected).to("arcsec") # this comes from running the example hor2eq but with the pressure set to 0 - radec_expected_noatm = SkyCoord('07h36m58.9s +15d25m37s', frame=radec_frame) - distance_noatm = radec_actual_noatm.separation(radec_expected_noatm).to('arcsec') + radec_expected_noatm = SkyCoord("07h36m58.9s +15d25m37s", frame=radec_frame) + distance_noatm = radec_actual_noatm.separation(radec_expected_noatm).to("arcsec") # The baseline difference is ~2.3 arcsec with one atm of pressure. The # difference is mainly due to the somewhat different atmospheric model that @@ -91,10 +99,10 @@ def run_pyephem(): import ephem observer = ephem.Observer() - observer.lon = -1 * np.radians(109 + 24/60. + 53.1/60**2) - observer.lat = np.radians(33 + 41/60. + 46.0/60.**2) + observer.lon = -1 * np.radians(109 + 24 / 60.0 + 53.1 / 60**2) + observer.lat = np.radians(33 + 41 / 60.0 + 46.0 / 60.0**2) observer.elevation = 300 - observer.date = 2455822.868055556-ephem.julian_date(0) + observer.date = 2455822.868055556 - ephem.julian_date(0) ra, dec = observer.radec_of(np.radians(6.8927), np.radians(60.7665)) print(f"EPHEM: {observer.date}: {np.degrees(ra)}, {np.degrees(dec)}") @@ -111,27 +119,31 @@ def test_against_pyephem(): https://gist.github.com/zonca/1672906 https://github.com/phn/pytpm/issues/2#issuecomment-3698679 """ - obstime = Time('2011-09-18 08:50:00') - location = EarthLocation(lon=Angle('-109d24m53.1s'), - lat=Angle('33d41m46.0s'), - height=300. * u.m) + obstime = Time("2011-09-18 08:50:00") + location = EarthLocation( + lon=Angle("-109d24m53.1s"), lat=Angle("33d41m46.0s"), height=300.0 * u.m + ) # We are using the default pressure and temperature in PyEphem # relative_humidity = ? # obswl = ? - altaz_frame = AltAz(obstime=obstime, location=location, - temperature=15 * u.deg_C, pressure=1.010 * u.bar) - - altaz = SkyCoord('6.8927d +60.7665d', frame=altaz_frame) - radec_actual = altaz.transform_to('icrs') - - radec_expected = SkyCoord('27.107480889479397d +62.512687777362046d', frame='icrs') - distance_ephem = radec_actual.separation(radec_expected).to('arcsec') + altaz_frame = AltAz( + obstime=obstime, + location=location, + temperature=15 * u.deg_C, + pressure=1.010 * u.bar, + ) + + altaz = SkyCoord("6.8927d +60.7665d", frame=altaz_frame) + radec_actual = altaz.transform_to("icrs") + + radec_expected = SkyCoord("27.107480889479397d +62.512687777362046d", frame="icrs") + distance_ephem = radec_actual.separation(radec_expected).to("arcsec") # 2021-04-06: 2.42 arcsec assert distance_ephem < 3 * u.arcsec # Add assert on current Astropy result so that we notice if something changes - radec_expected = SkyCoord('27.10602683d +62.51275391d', frame='icrs') - distance_astropy = radec_actual.separation(radec_expected).to('arcsec') + radec_expected = SkyCoord("27.10602683d +62.51275391d", frame="icrs") + distance_astropy = radec_actual.separation(radec_expected).to("arcsec") # 2021-04-06: 5e-6 arcsec (erfa 1.7.2 vs erfa 1.7.1). assert distance_astropy < 0.1 * u.arcsec @@ -143,17 +155,17 @@ def test_against_jpl_horizons(): (from the first row of the Results table at the bottom of that page) http://ssd.jpl.nasa.gov/?horizons_tutorial """ - obstime = Time('1998-07-28 03:00') - location = EarthLocation(lon=Angle('248.405300d'), - lat=Angle('31.9585d'), - height=2.06 * u.km) + obstime = Time("1998-07-28 03:00") + location = EarthLocation( + lon=Angle("248.405300d"), lat=Angle("31.9585d"), height=2.06 * u.km + ) # No atmosphere altaz_frame = AltAz(obstime=obstime, location=location) - altaz = SkyCoord('143.2970d 2.6223d', frame=altaz_frame) - radec_actual = altaz.transform_to('icrs') - radec_expected = SkyCoord('19h24m55.01s -40d56m28.9s', frame='icrs') - distance = radec_actual.separation(radec_expected).to('arcsec') + altaz = SkyCoord("143.2970d 2.6223d", frame=altaz_frame) + radec_actual = altaz.transform_to("icrs") + radec_expected = SkyCoord("19h24m55.01s -40d56m28.9s", frame="icrs") + distance = radec_actual.separation(radec_expected).to("arcsec") # 2021-04-06: astropy 4.2.1, erfa 1.7.1: 0.23919259 arcsec # 2021-04-06: astropy 4.3dev, erfa 1.7.2: 0.2391959 arcsec assert distance < 1 * u.arcsec @@ -166,21 +178,25 @@ def test_fk5_equinox_and_epoch_j2000_0_to_topocentric_observed(): """ # Observatory position for `kpno` from here: # http://idlastro.gsfc.nasa.gov/ftp/pro/astro/observatory.pro - location = EarthLocation(lon=Angle('-111.598333d'), - lat=Angle('31.956389d'), - height=2093.093 * u.m) # TODO: height correct? + location = EarthLocation( + lon=Angle("-111.598333d"), lat=Angle("31.956389d"), height=2093.093 * u.m + ) # TODO: height correct? - obstime = Time('2010-01-01 12:00:00') + obstime = Time("2010-01-01 12:00:00") # relative_humidity = ? # obswl = ? - altaz_frame = AltAz(obstime=obstime, location=location, - temperature=0 * u.deg_C, pressure=0.781 * u.bar) + altaz_frame = AltAz( + obstime=obstime, + location=location, + temperature=0 * u.deg_C, + pressure=0.781 * u.bar, + ) - radec = SkyCoord('12h22m54.899s 15d49m20.57s', frame='fk5') + radec = SkyCoord("12h22m54.899s 15d49m20.57s", frame="fk5") altaz_actual = radec.transform_to(altaz_frame) - altaz_expected = SkyCoord('264d55m06s 37d54m41s', frame='altaz') + altaz_expected = SkyCoord("264d55m06s 37d54m41s", frame="altaz") # altaz_expected = SkyCoord('343.586827647d 15.7683070508d', frame='altaz') # altaz_expected = SkyCoord('133.498195532d 22.0162383595d', frame='altaz') distance = altaz_actual.separation(altaz_expected) diff --git a/astropy/coordinates/tests/accuracy/test_ecliptic.py b/astropy/coordinates/tests/accuracy/test_ecliptic.py index 15fc9cdbfe9..b232412da11 100644 --- a/astropy/coordinates/tests/accuracy/test_ecliptic.py +++ b/astropy/coordinates/tests/accuracy/test_ecliptic.py @@ -33,13 +33,13 @@ def test_against_pytpm_doc_example(): Currently this is only testing against the example given in the pytpm docs """ - fk5_in = SkyCoord('12h22m54.899s', '15d49m20.57s', frame=FK5(equinox='J2000')) - pytpm_out = BarycentricMeanEcliptic(lon=178.78256462*u.deg, - lat=16.7597002513*u.deg, - equinox='J2000') + fk5_in = SkyCoord("12h22m54.899s", "15d49m20.57s", frame=FK5(equinox="J2000")) + pytpm_out = BarycentricMeanEcliptic( + lon=178.78256462 * u.deg, lat=16.7597002513 * u.deg, equinox="J2000" + ) astropy_out = fk5_in.transform_to(pytpm_out) - assert pytpm_out.separation(astropy_out) < (1*u.arcsec) + assert pytpm_out.separation(astropy_out) < (1 * u.arcsec) def test_ecliptic_heliobary(): @@ -47,31 +47,35 @@ def test_ecliptic_heliobary(): Check that the ecliptic transformations for heliocentric and barycentric at least more or less make sense """ - icrs = ICRS(1*u.deg, 2*u.deg, distance=1.5*R_sun) + icrs = ICRS(1 * u.deg, 2 * u.deg, distance=1.5 * R_sun) bary = icrs.transform_to(BarycentricMeanEcliptic()) helio = icrs.transform_to(HeliocentricMeanEcliptic()) # make sure there's a sizable distance shift - in 3d hundreds of km, but # this is 1D so we allow it to be somewhat smaller - assert np.abs(bary.distance - helio.distance) > 1*u.km + assert np.abs(bary.distance - helio.distance) > 1 * u.km # now make something that's got the location of helio but in bary's frame. # this is a convenience to allow `separation` to work as expected helio_in_bary_frame = bary.realize_frame(helio.cartesian) - assert bary.separation(helio_in_bary_frame) > 1*u.arcmin + assert bary.separation(helio_in_bary_frame) > 1 * u.arcmin -@pytest.mark.parametrize(('trueframe', 'meanframe'), - [(BarycentricTrueEcliptic, BarycentricMeanEcliptic), - (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), - (GeocentricTrueEcliptic, GeocentricMeanEcliptic), - (HeliocentricEclipticIAU76, HeliocentricMeanEcliptic)]) +@pytest.mark.parametrize( + ("trueframe", "meanframe"), + [ + (BarycentricTrueEcliptic, BarycentricMeanEcliptic), + (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), + (GeocentricTrueEcliptic, GeocentricMeanEcliptic), + (HeliocentricEclipticIAU76, HeliocentricMeanEcliptic), + ], +) def test_ecliptic_roundtrips(trueframe, meanframe): """ Check that the various ecliptic transformations at least roundtrip """ - icrs = ICRS(1*u.deg, 2*u.deg, distance=1.5*R_sun) + icrs = ICRS(1 * u.deg, 2 * u.deg, distance=1.5 * R_sun) truecoo = icrs.transform_to(trueframe()) meancoo = truecoo.transform_to(meanframe()) @@ -81,39 +85,51 @@ def test_ecliptic_roundtrips(trueframe, meanframe): assert quantity_allclose(truecoo.cartesian.xyz, truecoo2.cartesian.xyz) -@pytest.mark.parametrize(('trueframe', 'meanframe'), - [(BarycentricTrueEcliptic, BarycentricMeanEcliptic), - (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), - (GeocentricTrueEcliptic, GeocentricMeanEcliptic)]) +@pytest.mark.parametrize( + ("trueframe", "meanframe"), + [ + (BarycentricTrueEcliptic, BarycentricMeanEcliptic), + (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), + (GeocentricTrueEcliptic, GeocentricMeanEcliptic), + ], +) def test_ecliptic_true_mean_preserve_latitude(trueframe, meanframe): """ Check that the ecliptic true/mean transformations preserve latitude """ - truecoo = trueframe(90*u.deg, 0*u.deg, distance=1*u.AU) + truecoo = trueframe(90 * u.deg, 0 * u.deg, distance=1 * u.AU) meancoo = truecoo.transform_to(meanframe()) assert not quantity_allclose(truecoo.lon, meancoo.lon) - assert quantity_allclose(truecoo.lat, meancoo.lat, atol=1e-10*u.arcsec) + assert quantity_allclose(truecoo.lat, meancoo.lat, atol=1e-10 * u.arcsec) -@pytest.mark.parametrize('frame', - [HeliocentricMeanEcliptic, - HeliocentricTrueEcliptic, - HeliocentricEclipticIAU76]) +@pytest.mark.parametrize( + "frame", + [HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, HeliocentricEclipticIAU76], +) def test_helioecliptic_induced_velocity(frame): # Create a coordinate with zero speed in ICRS - time = Time('2021-01-01') - icrs = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.AU, - pm_ra_cosdec=0*u.deg/u.s, pm_dec=0*u.deg/u.s, radial_velocity=0*u.m/u.s) + time = Time("2021-01-01") + icrs = ICRS( + ra=1 * u.deg, + dec=2 * u.deg, + distance=3 * u.AU, + pm_ra_cosdec=0 * u.deg / u.s, + pm_dec=0 * u.deg / u.s, + radial_velocity=0 * u.m / u.s, + ) # Transforming to a helioecliptic frame should give an induced speed equal to the Sun's speed transformed = icrs.transform_to(frame(obstime=time)) - _, vel = get_body_barycentric_posvel('sun', time) + _, vel = get_body_barycentric_posvel("sun", time) assert quantity_allclose(transformed.velocity.norm(), vel.norm()) # Transforming back to ICRS should get back to zero speed back = transformed.transform_to(ICRS()) - assert quantity_allclose(back.velocity.norm(), 0*u.m/u.s, atol=1e-10*u.m/u.s) + assert quantity_allclose( + back.velocity.norm(), 0 * u.m / u.s, atol=1e-10 * u.m / u.s + ) def test_ecl_geo(): @@ -122,7 +138,7 @@ def test_ecl_geo(): true "accuracy" test we need a comparison dataset that is similar to the geocentric/GCRS comparison we want to do here. Contributions welcome! """ - gcrs = GCRS(10*u.deg, 20*u.deg, distance=1.5*R_earth) + gcrs = GCRS(10 * u.deg, 20 * u.deg, distance=1.5 * R_earth) gecl = gcrs.transform_to(GeocentricMeanEcliptic()) assert quantity_allclose(gecl.distance, gcrs.distance) @@ -133,9 +149,9 @@ def test_arraytransforms(): Test that transforms to/from ecliptic coordinates work on array coordinates (not testing for accuracy.) """ - ra = np.ones((4, ), dtype=float) * u.deg - dec = 2*np.ones((4, ), dtype=float) * u.deg - distance = np.ones((4, ), dtype=float) * u.au + ra = np.ones((4,), dtype=float) * u.deg + dec = 2 * np.ones((4,), dtype=float) * u.deg + distance = np.ones((4,), dtype=float) * u.au test_icrs = ICRS(ra=ra, dec=dec, distance=distance) test_gcrs = GCRS(test_icrs.data) @@ -161,7 +177,7 @@ def test_arraytransforms(): def test_roundtrip_scalar(): - icrs = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.au) + icrs = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=3 * u.au) gcrs = GCRS(icrs.cartesian) bary = icrs.transform_to(BarycentricMeanEcliptic()) @@ -177,14 +193,20 @@ def test_roundtrip_scalar(): assert quantity_allclose(geo_gcrs.cartesian.xyz, gcrs.cartesian.xyz) -@pytest.mark.parametrize('frame', - [HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, - GeocentricMeanEcliptic, GeocentricTrueEcliptic, - HeliocentricEclipticIAU76]) +@pytest.mark.parametrize( + "frame", + [ + HeliocentricMeanEcliptic, + HeliocentricTrueEcliptic, + GeocentricMeanEcliptic, + GeocentricTrueEcliptic, + HeliocentricEclipticIAU76, + ], +) def test_loopback_obstime(frame): # Test that the loopback properly handles a change in obstime - from_coo = frame(1*u.deg, 2*u.deg, 3*u.AU, obstime='2001-01-01') - to_frame = frame(obstime='2001-06-30') + from_coo = frame(1 * u.deg, 2 * u.deg, 3 * u.AU, obstime="2001-01-01") + to_frame = frame(obstime="2001-06-30") explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) @@ -200,14 +222,21 @@ def test_loopback_obstime(frame): assert quantity_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10) -@pytest.mark.parametrize('frame', - [BarycentricMeanEcliptic, BarycentricTrueEcliptic, - HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, - GeocentricMeanEcliptic, GeocentricTrueEcliptic]) +@pytest.mark.parametrize( + "frame", + [ + BarycentricMeanEcliptic, + BarycentricTrueEcliptic, + HeliocentricMeanEcliptic, + HeliocentricTrueEcliptic, + GeocentricMeanEcliptic, + GeocentricTrueEcliptic, + ], +) def test_loopback_equinox(frame): # Test that the loopback properly handles a change in equinox - from_coo = frame(1*u.deg, 2*u.deg, 3*u.AU, equinox='2001-01-01') - to_frame = frame(equinox='2001-06-30') + from_coo = frame(1 * u.deg, 2 * u.deg, 3 * u.AU, equinox="2001-01-01") + to_frame = frame(equinox="2001-06-30") explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) @@ -225,8 +254,10 @@ def test_loopback_equinox(frame): def test_loopback_obliquity(): # Test that the loopback properly handles a change in obliquity - from_coo = CustomBarycentricEcliptic(1*u.deg, 2*u.deg, 3*u.AU, obliquity=84000*u.arcsec) - to_frame = CustomBarycentricEcliptic(obliquity=85000*u.arcsec) + from_coo = CustomBarycentricEcliptic( + 1 * u.deg, 2 * u.deg, 3 * u.AU, obliquity=84000 * u.arcsec + ) + to_frame = CustomBarycentricEcliptic(obliquity=85000 * u.arcsec) explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) diff --git a/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk4.py b/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk4.py index 5cb13306de0..ca45107bd8f 100644 --- a/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk4.py +++ b/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk4.py @@ -16,12 +16,12 @@ # It looks as though SLALIB, which AST relies on, assumes a simplified version # of the e-terms correction, so we have to up the tolerance a bit to get things # to agree. -TOLERANCE = 1.e-5 # arcseconds +TOLERANCE = 1.0e-5 # arcseconds def test_fk4_no_e_fk4(): - lines = get_pkg_data_contents('data/fk4_no_e_fk4.csv').split('\n') - t = Table.read(lines, format='ascii', delimiter=',', guess=False) + lines = get_pkg_data_contents("data/fk4_no_e_fk4.csv").split("\n") + t = Table.read(lines, format="ascii", delimiter=",", guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) @@ -35,27 +35,36 @@ def test_fk4_no_e_fk4(): r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # FK4 to FK4NoETerms - c1 = FK4(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, - obstime=Time(r['obstime'])) + c1 = FK4( + ra=r["ra_in"] * u.deg, dec=r["dec_in"] * u.deg, obstime=Time(r["obstime"]) + ) c2 = c1.transform_to(FK4NoETerms()) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk4ne']), np.radians(r['dec_fk4ne'])) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk4ne"]), + np.radians(r["dec_fk4ne"]), + ) - diffarcsec1.append(np.degrees(diff) * 3600.) + diffarcsec1.append(np.degrees(diff) * 3600.0) # FK4NoETerms to FK4 - c1 = FK4NoETerms(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, - obstime=Time(r['obstime'])) + c1 = FK4NoETerms( + ra=r["ra_in"] * u.deg, dec=r["dec_in"] * u.deg, obstime=Time(r["obstime"]) + ) c2 = c1.transform_to(FK4()) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk4']), - np.radians(r['dec_fk4'])) - - diffarcsec2.append(np.degrees(diff) * 3600.) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk4"]), + np.radians(r["dec_fk4"]), + ) + + diffarcsec2.append(np.degrees(diff) * 3600.0) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE) diff --git a/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk5.py b/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk5.py index b7be4bab36b..ea605d7b119 100644 --- a/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk5.py +++ b/astropy/coordinates/tests/accuracy/test_fk4_no_e_fk5.py @@ -17,8 +17,8 @@ def test_fk4_no_e_fk5(): - lines = get_pkg_data_contents('data/fk4_no_e_fk5.csv').split('\n') - t = Table.read(lines, format='ascii', delimiter=',', guess=False) + lines = get_pkg_data_contents("data/fk4_no_e_fk5.csv").split("\n") + t = Table.read(lines, format="ascii", delimiter=",", guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) @@ -32,31 +32,44 @@ def test_fk4_no_e_fk5(): r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # FK4NoETerms to FK5 - c1 = FK4NoETerms(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, - obstime=Time(r['obstime']), - equinox=Time(r['equinox_fk4'])) - c2 = c1.transform_to(FK5(equinox=Time(r['equinox_fk5']))) + c1 = FK4NoETerms( + ra=r["ra_in"] * u.deg, + dec=r["dec_in"] * u.deg, + obstime=Time(r["obstime"]), + equinox=Time(r["equinox_fk4"]), + ) + c2 = c1.transform_to(FK5(equinox=Time(r["equinox_fk5"]))) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk5']), - np.radians(r['dec_fk5'])) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk5"]), + np.radians(r["dec_fk5"]), + ) - diffarcsec1.append(np.degrees(diff) * 3600.) + diffarcsec1.append(np.degrees(diff) * 3600.0) # FK5 to FK4NoETerms - c1 = FK5(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, - equinox=Time(r['equinox_fk5'])) - fk4neframe = FK4NoETerms(obstime=Time(r['obstime']), - equinox=Time(r['equinox_fk4'])) + c1 = FK5( + ra=r["ra_in"] * u.deg, + dec=r["dec_in"] * u.deg, + equinox=Time(r["equinox_fk5"]), + ) + fk4neframe = FK4NoETerms( + obstime=Time(r["obstime"]), equinox=Time(r["equinox_fk4"]) + ) c2 = c1.transform_to(fk4neframe) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk4']), - np.radians(r['dec_fk4'])) - - diffarcsec2.append(np.degrees(diff) * 3600.) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk4"]), + np.radians(r["dec_fk4"]), + ) + + diffarcsec2.append(np.degrees(diff) * 3600.0) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE) diff --git a/astropy/coordinates/tests/accuracy/test_galactic_fk4.py b/astropy/coordinates/tests/accuracy/test_galactic_fk4.py index 3e4733f438f..17898fccacb 100644 --- a/astropy/coordinates/tests/accuracy/test_galactic_fk4.py +++ b/astropy/coordinates/tests/accuracy/test_galactic_fk4.py @@ -17,8 +17,8 @@ def test_galactic_fk4(): - lines = get_pkg_data_contents('data/galactic_fk4.csv').split('\n') - t = Table.read(lines, format='ascii', delimiter=',', guess=False) + lines = get_pkg_data_contents("data/galactic_fk4.csv").split("\n") + t = Table.read(lines, format="ascii", delimiter=",", guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) @@ -32,28 +32,34 @@ def test_galactic_fk4(): r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # Galactic to FK4 - c1 = Galactic(l=r['lon_in']*u.deg, b=r['lat_in']*u.deg) - c2 = c1.transform_to(FK4(equinox=Time(r['equinox_fk4']))) + c1 = Galactic(l=r["lon_in"] * u.deg, b=r["lat_in"] * u.deg) + c2 = c1.transform_to(FK4(equinox=Time(r["equinox_fk4"]))) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk4']), - np.radians(r['dec_fk4'])) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk4"]), + np.radians(r["dec_fk4"]), + ) - diffarcsec1.append(np.degrees(diff) * 3600.) + diffarcsec1.append(np.degrees(diff) * 3600.0) # FK4 to Galactic - c1 = FK4(ra=r['lon_in']*u.deg, dec=r['lat_in']*u.deg, - obstime=Time(r['obstime']), - equinox=Time(r['equinox_fk4'])) + c1 = FK4( + ra=r["lon_in"] * u.deg, + dec=r["lat_in"] * u.deg, + obstime=Time(r["obstime"]), + equinox=Time(r["equinox_fk4"]), + ) c2 = c1.transform_to(Galactic()) # Find difference - diff = angular_separation(c2.l.radian, c2.b.radian, - np.radians(r['lon_gal']), - np.radians(r['lat_gal'])) + diff = angular_separation( + c2.l.radian, c2.b.radian, np.radians(r["lon_gal"]), np.radians(r["lat_gal"]) + ) - diffarcsec2.append(np.degrees(diff) * 3600.) + diffarcsec2.append(np.degrees(diff) * 3600.0) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE) diff --git a/astropy/coordinates/tests/accuracy/test_icrs_fk5.py b/astropy/coordinates/tests/accuracy/test_icrs_fk5.py index ce0f0c6b1b8..3207150dcf1 100644 --- a/astropy/coordinates/tests/accuracy/test_icrs_fk5.py +++ b/astropy/coordinates/tests/accuracy/test_icrs_fk5.py @@ -17,8 +17,8 @@ def test_icrs_fk5(): - lines = get_pkg_data_contents('data/icrs_fk5.csv').split('\n') - t = Table.read(lines, format='ascii', delimiter=',', guess=False) + lines = get_pkg_data_contents("data/icrs_fk5.csv").split("\n") + t = Table.read(lines, format="ascii", delimiter=",", guess=False) if N_ACCURACY_TESTS >= len(t): idxs = range(len(t)) @@ -32,27 +32,36 @@ def test_icrs_fk5(): r = t[int(i)] # int here is to get around a py 3.x astropy.table bug # ICRS to FK5 - c1 = ICRS(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg) - c2 = c1.transform_to(FK5(equinox=Time(r['equinox_fk5']))) + c1 = ICRS(ra=r["ra_in"] * u.deg, dec=r["dec_in"] * u.deg) + c2 = c1.transform_to(FK5(equinox=Time(r["equinox_fk5"]))) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_fk5']), - np.radians(r['dec_fk5'])) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_fk5"]), + np.radians(r["dec_fk5"]), + ) - diffarcsec1.append(np.degrees(diff) * 3600.) + diffarcsec1.append(np.degrees(diff) * 3600.0) # FK5 to ICRS - c1 = FK5(ra=r['ra_in']*u.deg, dec=r['dec_in']*u.deg, - equinox=Time(r['equinox_fk5'])) + c1 = FK5( + ra=r["ra_in"] * u.deg, + dec=r["dec_in"] * u.deg, + equinox=Time(r["equinox_fk5"]), + ) c2 = c1.transform_to(ICRS()) # Find difference - diff = angular_separation(c2.ra.radian, c2.dec.radian, - np.radians(r['ra_icrs']), - np.radians(r['dec_icrs'])) - - diffarcsec2.append(np.degrees(diff) * 3600.) + diff = angular_separation( + c2.ra.radian, + c2.dec.radian, + np.radians(r["ra_icrs"]), + np.radians(r["dec_icrs"]), + ) + + diffarcsec2.append(np.degrees(diff) * 3600.0) np.testing.assert_array_less(diffarcsec1, TOLERANCE) np.testing.assert_array_less(diffarcsec2, TOLERANCE) diff --git a/astropy/coordinates/tests/helper.py b/astropy/coordinates/tests/helper.py index 3ea81c10436..220a9c44a12 100644 --- a/astropy/coordinates/tests/helper.py +++ b/astropy/coordinates/tests/helper.py @@ -3,8 +3,7 @@ def skycoord_equal(sc1, sc2): - """SkyCoord equality useful for testing - """ + """SkyCoord equality useful for testing""" if not sc1.is_equivalent_frame(sc2): return False if sc1.representation_type is not sc2.representation_type: diff --git a/astropy/coordinates/tests/test_angle_generators.py b/astropy/coordinates/tests/test_angle_generators.py index 9db01b01def..cef46b140b3 100644 --- a/astropy/coordinates/tests/test_angle_generators.py +++ b/astropy/coordinates/tests/test_angle_generators.py @@ -16,8 +16,9 @@ def test_golden_spiral_grid_input(): assert len(usph) == 100 -@pytest.mark.parametrize("func", [uniform_spherical_random_surface, - uniform_spherical_random_volume]) +@pytest.mark.parametrize( + "func", [uniform_spherical_random_surface, uniform_spherical_random_volume] +) def test_uniform_spherical_random_input(func): with NumpyRNGContext(42): sph = func(size=100) @@ -29,8 +30,8 @@ def test_uniform_spherical_random_volume_input(): sph = uniform_spherical_random_volume(size=100, max_radius=1) assert len(sph) == 100 assert sph.distance.unit == u.dimensionless_unscaled - assert sph.distance.max() <= 1. + assert sph.distance.max() <= 1.0 - sph = uniform_spherical_random_volume(size=100, max_radius=4*u.pc) + sph = uniform_spherical_random_volume(size=100, max_radius=4 * u.pc) assert len(sph) == 100 - assert sph.distance.max() <= 4*u.pc + assert sph.distance.max() <= 4 * u.pc diff --git a/astropy/coordinates/tests/test_angles.py b/astropy/coordinates/tests/test_angles.py index 4feaaa1a515..2b78c2f3bd6 100644 --- a/astropy/coordinates/tests/test_angles.py +++ b/astropy/coordinates/tests/test_angles.py @@ -25,13 +25,13 @@ def test_create_angles(): Tests creating and accessing Angle objects """ - ''' The "angle" is a fundamental object. The internal + """ The "angle" is a fundamental object. The internal representation is stored in radians, but this is transparent to the user. Units *must* be specified rather than a default value be assumed. This is as much for self-documenting code as anything else. Angle objects simply represent a single angular coordinate. More specific - angular coordinates (e.g. Longitude, Latitude) are subclasses of Angle.''' + angular coordinates (e.g. Longitude, Latitude) are subclasses of Angle.""" a1 = Angle(54.12412, unit=u.degree) a2 = Angle("54.12412", unit=u.degree) @@ -46,11 +46,11 @@ def test_create_angles(): a10 = Angle(3.60827466667, unit=u.hour) a11 = Angle("3:36:29.7888000120", unit=u.hour) - with pytest.warns(AstropyDeprecationWarning, match='hms_to_hour'): + with pytest.warns(AstropyDeprecationWarning, match="hms_to_hour"): a12 = Angle((3, 36, 29.7888000120), unit=u.hour) # *must* be a tuple - with pytest.warns(AstropyDeprecationWarning, match='hms_to_hour'): + with pytest.warns(AstropyDeprecationWarning, match="hms_to_hour"): # Regression test for #5001 - a13 = Angle((3, 36, 29.7888000120), unit='hour') + a13 = Angle((3, 36, 29.7888000120), unit="hour") Angle(0.944644098745, unit=u.radian) @@ -82,7 +82,7 @@ def test_create_angles(): a22 = Angle("3.6h", unit=u.hour) a23 = Angle("- 3h", unit=u.hour) a24 = Angle("+ 3h", unit=u.hour) - a25 = Angle(3., unit=u.hour**1) + a25 = Angle(3.0, unit=u.hour**1) # ensure the above angles that should match do assert a1 == a2 == a3 == a4 == a5 == a6 == a8 == a18 == a19 == a20 @@ -124,7 +124,7 @@ def test_create_angles(): def test_angle_from_view(): - q = np.arange(3.) * u.deg + q = np.arange(3.0) * u.deg a = q.view(Angle) assert type(a) is Angle assert a.unit is q.unit @@ -172,15 +172,15 @@ def test_angle_ops(): assert a1 <= a5 # check operations with non-angular result give Quantity. - a6 = Angle(45., u.degree) + a6 = Angle(45.0, u.degree) a7 = a6 * a5 assert type(a7) is u.Quantity # but those with angular result yield Angle. # (a9 is regression test for #5327) - a8 = a1 + 1.*u.deg + a8 = a1 + 1.0 * u.deg assert type(a8) is Angle - a9 = 1.*u.deg + a1 + a9 = 1.0 * u.deg + a1 assert type(a9) is Angle with pytest.raises(TypeError): @@ -196,25 +196,25 @@ def test_angle_ops(): def test_angle_methods(): # Most methods tested as part of the Quantity tests. # A few tests here which caused problems before: #8368 - a = Angle([0., 2.], 'deg') + a = Angle([0.0, 2.0], "deg") a_mean = a.mean() assert type(a_mean) is Angle - assert a_mean == 1. * u.degree + assert a_mean == 1.0 * u.degree a_std = a.std() assert type(a_std) is Angle - assert a_std == 1. * u.degree + assert a_std == 1.0 * u.degree a_var = a.var() assert type(a_var) is u.Quantity - assert a_var == 1. * u.degree ** 2 + assert a_var == 1.0 * u.degree**2 a_ptp = a.ptp() assert type(a_ptp) is Angle - assert a_ptp == 2. * u.degree + assert a_ptp == 2.0 * u.degree a_max = a.max() assert type(a_max) is Angle - assert a_max == 2. * u.degree + assert a_max == 2.0 * u.degree a_min = a.min() assert type(a_min) is Angle - assert a_min == 0. * u.degree + assert a_min == 0.0 * u.degree def test_angle_convert(): @@ -267,7 +267,7 @@ def test_angle_formatting(): Tests string formatting for Angle objects """ - ''' + """ The string method of Angle has this signature: def string(self, unit=DEGREE, decimal=False, sep=" ", precision=5, pad=False): @@ -275,100 +275,113 @@ def string(self, unit=DEGREE, decimal=False, sep=" ", precision=5, The "decimal" parameter defaults to False since if you need to print the Angle as a decimal, there's no need to use the "format" method (see above). - ''' + """ angle = Angle("54.12412", unit=u.degree) # __str__ is the default `format` assert str(angle) == angle.to_string() - res = 'Angle as HMS: 3h36m29.7888s' + res = "Angle as HMS: 3h36m29.7888s" assert f"Angle as HMS: {angle.to_string(unit=u.hour)}" == res - res = 'Angle as HMS: 3:36:29.7888' + res = "Angle as HMS: 3:36:29.7888" assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':')}" == res - res = 'Angle as HMS: 3:36:29.79' + res = "Angle as HMS: 3:36:29.79" assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':', precision=2)}" == res # Note that you can provide one, two, or three separators passed as a # tuple or list - res = 'Angle as HMS: 3h36m29.7888s' - assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour, - sep=("h", "m", "s"), - precision=4)) == res + res = "Angle as HMS: 3h36m29.7888s" + assert ( + "Angle as HMS:" + f" {angle.to_string(unit=u.hour, sep=('h', 'm', 's'), precision=4)}" == res + ) - res = 'Angle as HMS: 3-36|29.7888' - assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour, sep=["-", "|"], - precision=4)) == res + res = "Angle as HMS: 3-36|29.7888" + assert ( + f"Angle as HMS: {angle.to_string(unit=u.hour, sep=['-', '|'], precision=4)}" + == res + ) - res = 'Angle as HMS: 3-36-29.7888' + res = "Angle as HMS: 3-36-29.7888" assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep='-', precision=4)}" == res - res = 'Angle as HMS: 03h36m29.7888s' + res = "Angle as HMS: 03h36m29.7888s" assert f"Angle as HMS: {angle.to_string(unit=u.hour, precision=4, pad=True)}" == res # Same as above, in degrees angle = Angle("3 36 29.78880", unit=u.degree) - res = 'Angle as DMS: 3d36m29.7888s' + res = "Angle as DMS: 3d36m29.7888s" assert f"Angle as DMS: {angle.to_string(unit=u.degree)}" == res - res = 'Angle as DMS: 3:36:29.7888' + res = "Angle as DMS: 3:36:29.7888" assert f"Angle as DMS: {angle.to_string(unit=u.degree, sep=':')}" == res - res = 'Angle as DMS: 3:36:29.79' - assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=":", - precision=2)) == res + res = "Angle as DMS: 3:36:29.79" + assert ( + f"Angle as DMS: {angle.to_string(unit=u.degree, sep=':', precision=2)}" == res + ) # Note that you can provide one, two, or three separators passed as a # tuple or list - res = 'Angle as DMS: 3d36m29.7888s' - assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, - sep=("d", "m", "s"), - precision=4)) == res - - res = 'Angle as DMS: 3-36|29.7888' - assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=["-", "|"], - precision=4)) == res - - res = 'Angle as DMS: 3-36-29.7888' - assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep="-", - precision=4)) == res - - res = 'Angle as DMS: 03d36m29.7888s' - assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, precision=4, - pad=True)) == res - - res = 'Angle as rad: 0.0629763rad' + res = "Angle as DMS: 3d36m29.7888s" + assert ( + f"Angle as DMS: {angle.to_string(unit=u.deg, sep=('d', 'm', 's'), precision=4)}" + == res + ) + + res = "Angle as DMS: 3-36|29.7888" + assert ( + f"Angle as DMS: {angle.to_string(unit=u.degree, sep=['-', '|'], precision=4)}" + == res + ) + + res = "Angle as DMS: 3-36-29.7888" + assert ( + f"Angle as DMS: {angle.to_string(unit=u.degree, sep='-', precision=4)}" == res + ) + + res = "Angle as DMS: 03d36m29.7888s" + assert ( + f"Angle as DMS: {angle.to_string(unit=u.degree, precision=4, pad=True)}" == res + ) + + res = "Angle as rad: 0.0629763rad" assert f"Angle as rad: {angle.to_string(unit=u.radian)}" == res - res = 'Angle as rad decimal: 0.0629763' - assert f"Angle as rad decimal: {angle.to_string(unit=u.radian, decimal=True)}" == res + res = "Angle as rad decimal: 0.0629763" + assert ( + f"Angle as rad decimal: {angle.to_string(unit=u.radian, decimal=True)}" == res + ) # check negative angles angle = Angle(-1.23456789, unit=u.degree) angle2 = Angle(-1.23456789, unit=u.hour) - assert angle.to_string() == '-1d14m04.444404s' - assert angle.to_string(pad=True) == '-01d14m04.444404s' - assert angle.to_string(unit=u.hour) == '-0h04m56.2962936s' - assert angle2.to_string(unit=u.hour, pad=True) == '-01h14m04.444404s' - assert angle.to_string(unit=u.radian, decimal=True) == '-0.0215473' + assert angle.to_string() == "-1d14m04.444404s" + assert angle.to_string(pad=True) == "-01d14m04.444404s" + assert angle.to_string(unit=u.hour) == "-0h04m56.2962936s" + assert angle2.to_string(unit=u.hour, pad=True) == "-01h14m04.444404s" + assert angle.to_string(unit=u.radian, decimal=True) == "-0.0215473" # We should recognize units that are equal but not identical - assert angle.to_string(unit=u.hour**1) == '-0h04m56.2962936s' + assert angle.to_string(unit=u.hour**1) == "-0h04m56.2962936s" def test_to_string_vector(): # Regression test for the fact that vectorize doesn't work with Numpy 1.6 - assert Angle([1./7., 1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s" - assert Angle([1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s" - assert Angle(1./7., unit='deg').to_string() == "0d08m34.28571429s" + assert ( + Angle([1.0 / 7.0, 1.0 / 7.0], unit="deg").to_string()[0] == "0d08m34.28571429s" + ) + assert Angle([1.0 / 7.0], unit="deg").to_string()[0] == "0d08m34.28571429s" + assert Angle(1.0 / 7.0, unit="deg").to_string() == "0d08m34.28571429s" def test_angle_format_roundtripping(): @@ -380,7 +393,7 @@ def test_angle_format_roundtripping(): a1 = Angle(0, unit=u.radian) a2 = Angle(10, unit=u.degree) a3 = Angle(0.543, unit=u.degree) - a4 = Angle('1d2m3.4s') + a4 = Angle("1d2m3.4s") assert Angle(str(a1)).degree == a1.degree assert Angle(str(a2)).degree == a2.degree @@ -388,8 +401,8 @@ def test_angle_format_roundtripping(): assert Angle(str(a4)).degree == a4.degree # also check Longitude/Latitude - ra = Longitude('1h2m3.4s') - dec = Latitude('1d2m3.4s') + ra = Longitude("1h2m3.4s") + dec = Latitude("1d2m3.4s") assert_allclose(Angle(str(ra)).degree, ra.degree) assert_allclose(Angle(str(dec)).degree, dec.degree) @@ -400,7 +413,7 @@ def test_radec(): Tests creation/operations of Longitude and Latitude objects """ - ''' + """ Longitude and Latitude are objects that are subclassed from Angle. As with Angle, Longitude and Latitude can parse any unambiguous format (tuples, formatted strings, etc.). @@ -409,7 +422,7 @@ def test_radec(): are so prevalent in astronomy that it's worth creating ones for these units. They will be noted as "special" in the docs and use of the just the Angle class is to be used for other coordinate systems. - ''' + """ with pytest.raises(u.UnitsError): ra = Longitude("4:08:15.162342") # error - hours or degrees? @@ -444,7 +457,7 @@ def test_radec(): ra = Longitude((56, 14, 52.52)) with pytest.raises(u.UnitsError): ra = Longitude((12, 14, 52)) # ambiguous w/o units - with pytest.warns(AstropyDeprecationWarning, match='hms_to_hours'): + with pytest.warns(AstropyDeprecationWarning, match="hms_to_hours"): ra = Longitude((12, 14, 52), unit=u.hour) # Units can be specified @@ -461,136 +474,136 @@ def test_radec(): def test_negative_zero_dms(): # Test for DMS parser - a = Angle('-00:00:10', u.deg) - assert_allclose(a.degree, -10. / 3600.) + a = Angle("-00:00:10", u.deg) + assert_allclose(a.degree, -10.0 / 3600.0) # Unicode minus - a = Angle('−00:00:10', u.deg) - assert_allclose(a.degree, -10. / 3600.) + a = Angle("−00:00:10", u.deg) + assert_allclose(a.degree, -10.0 / 3600.0) def test_negative_zero_dm(): # Test for DM parser - a = Angle('-00:10', u.deg) - assert_allclose(a.degree, -10. / 60.) + a = Angle("-00:10", u.deg) + assert_allclose(a.degree, -10.0 / 60.0) def test_negative_zero_hms(): # Test for HMS parser - a = Angle('-00:00:10', u.hour) - assert_allclose(a.hour, -10. / 3600.) + a = Angle("-00:00:10", u.hour) + assert_allclose(a.hour, -10.0 / 3600.0) def test_negative_zero_hm(): # Test for HM parser - a = Angle('-00:10', u.hour) - assert_allclose(a.hour, -10. / 60.) + a = Angle("-00:10", u.hour) + assert_allclose(a.hour, -10.0 / 60.0) def test_negative_sixty_hm(): # Test for HM parser with pytest.warns(IllegalMinuteWarning): - a = Angle('-00:60', u.hour) - assert_allclose(a.hour, -1.) + a = Angle("-00:60", u.hour) + assert_allclose(a.hour, -1.0) def test_plus_sixty_hm(): # Test for HM parser with pytest.warns(IllegalMinuteWarning): - a = Angle('00:60', u.hour) - assert_allclose(a.hour, 1.) + a = Angle("00:60", u.hour) + assert_allclose(a.hour, 1.0) def test_negative_fifty_nine_sixty_dms(): # Test for DMS parser with pytest.warns(IllegalSecondWarning): - a = Angle('-00:59:60', u.deg) - assert_allclose(a.degree, -1.) + a = Angle("-00:59:60", u.deg) + assert_allclose(a.degree, -1.0) def test_plus_fifty_nine_sixty_dms(): # Test for DMS parser with pytest.warns(IllegalSecondWarning): - a = Angle('+00:59:60', u.deg) - assert_allclose(a.degree, 1.) + a = Angle("+00:59:60", u.deg) + assert_allclose(a.degree, 1.0) def test_negative_sixty_dms(): # Test for DMS parser with pytest.warns(IllegalSecondWarning): - a = Angle('-00:00:60', u.deg) - assert_allclose(a.degree, -1. / 60.) + a = Angle("-00:00:60", u.deg) + assert_allclose(a.degree, -1.0 / 60.0) def test_plus_sixty_dms(): # Test for DMS parser with pytest.warns(IllegalSecondWarning): - a = Angle('+00:00:60', u.deg) - assert_allclose(a.degree, 1. / 60.) + a = Angle("+00:00:60", u.deg) + assert_allclose(a.degree, 1.0 / 60.0) def test_angle_to_is_angle(): with pytest.warns(IllegalSecondWarning): - a = Angle('00:00:60', u.deg) + a = Angle("00:00:60", u.deg) assert isinstance(a, Angle) assert isinstance(a.to(u.rad), Angle) def test_angle_to_quantity(): with pytest.warns(IllegalSecondWarning): - a = Angle('00:00:60', u.deg) + a = Angle("00:00:60", u.deg) q = u.Quantity(a) assert isinstance(q, u.Quantity) assert q.unit is u.deg def test_quantity_to_angle(): - a = Angle(1.0*u.deg) + a = Angle(1.0 * u.deg) assert isinstance(a, Angle) with pytest.raises(u.UnitsError): - Angle(1.0*u.meter) - a = Angle(1.0*u.hour) + Angle(1.0 * u.meter) + a = Angle(1.0 * u.hour) assert isinstance(a, Angle) assert a.unit is u.hourangle with pytest.raises(u.UnitsError): - Angle(1.0*u.min) + Angle(1.0 * u.min) def test_angle_string(): with pytest.warns(IllegalSecondWarning): - a = Angle('00:00:60', u.deg) - assert str(a) == '0d01m00s' - a = Angle('00:00:59S', u.deg) - assert str(a) == '-0d00m59s' - a = Angle('00:00:59N', u.deg) - assert str(a) == '0d00m59s' - a = Angle('00:00:59E', u.deg) - assert str(a) == '0d00m59s' - a = Angle('00:00:59W', u.deg) - assert str(a) == '-0d00m59s' - a = Angle('-00:00:10', u.hour) - assert str(a) == '-0h00m10s' - a = Angle('00:00:59E', u.hour) - assert str(a) == '0h00m59s' - a = Angle('00:00:59W', u.hour) - assert str(a) == '-0h00m59s' + a = Angle("00:00:60", u.deg) + assert str(a) == "0d01m00s" + a = Angle("00:00:59S", u.deg) + assert str(a) == "-0d00m59s" + a = Angle("00:00:59N", u.deg) + assert str(a) == "0d00m59s" + a = Angle("00:00:59E", u.deg) + assert str(a) == "0d00m59s" + a = Angle("00:00:59W", u.deg) + assert str(a) == "-0d00m59s" + a = Angle("-00:00:10", u.hour) + assert str(a) == "-0h00m10s" + a = Angle("00:00:59E", u.hour) + assert str(a) == "0h00m59s" + a = Angle("00:00:59W", u.hour) + assert str(a) == "-0h00m59s" a = Angle(3.2, u.radian) - assert str(a) == '3.2rad' + assert str(a) == "3.2rad" a = Angle(4.2, u.microarcsecond) - assert str(a) == '4.2uarcsec' - a = Angle('1.0uarcsec') + assert str(a) == "4.2uarcsec" + a = Angle("1.0uarcsec") assert a.value == 1.0 assert a.unit == u.microarcsecond - a = Angle('1.0uarcsecN') + a = Angle("1.0uarcsecN") assert a.value == 1.0 assert a.unit == u.microarcsecond - a = Angle('1.0uarcsecS') + a = Angle("1.0uarcsecS") assert a.value == -1.0 assert a.unit == u.microarcsecond - a = Angle('1.0uarcsecE') + a = Angle("1.0uarcsecE") assert a.value == 1.0 assert a.unit == u.microarcsecond - a = Angle('1.0uarcsecW') + a = Angle("1.0uarcsecW") assert a.value == -1.0 assert a.unit == u.microarcsecond a = Angle("3d") @@ -623,32 +636,32 @@ def test_angle_string(): a = Angle("10'W") assert_allclose(a.value, -10.0) assert a.unit == u.arcminute - a = Angle('45°55′12″N') - assert str(a) == '45d55m12s' + a = Angle("45°55′12″N") + assert str(a) == "45d55m12s" assert_allclose(a.value, 45.92) assert a.unit == u.deg - a = Angle('45°55′12″S') - assert str(a) == '-45d55m12s' + a = Angle("45°55′12″S") + assert str(a) == "-45d55m12s" assert_allclose(a.value, -45.92) assert a.unit == u.deg - a = Angle('45°55′12″E') - assert str(a) == '45d55m12s' + a = Angle("45°55′12″E") + assert str(a) == "45d55m12s" assert_allclose(a.value, 45.92) assert a.unit == u.deg - a = Angle('45°55′12″W') - assert str(a) == '-45d55m12s' + a = Angle("45°55′12″W") + assert str(a) == "-45d55m12s" assert_allclose(a.value, -45.92) assert a.unit == u.deg with pytest.raises(ValueError): - Angle('00h00m10sN') + Angle("00h00m10sN") with pytest.raises(ValueError): - Angle('45°55′12″NS') + Angle("45°55′12″NS") def test_angle_repr(): - assert 'Angle' in repr(Angle(0, u.deg)) - assert 'Longitude' in repr(Longitude(0, u.deg)) - assert 'Latitude' in repr(Latitude(0, u.deg)) + assert "Angle" in repr(Angle(0, u.deg)) + assert "Longitude" in repr(Longitude(0, u.deg)) + assert "Latitude" in repr(Latitude(0, u.deg)) a = Angle(0, u.deg) repr(a) @@ -668,48 +681,48 @@ def test_large_angle_representation(): def test_wrap_at_inplace(): a = Angle([-20, 150, 350, 360] * u.deg) - out = a.wrap_at('180d', inplace=True) + out = a.wrap_at("180d", inplace=True) assert out is None - assert np.all(a.degree == np.array([-20., 150., -10., 0.])) + assert np.all(a.degree == np.array([-20.0, 150.0, -10.0, 0.0])) def test_latitude(): with pytest.raises(ValueError): - lat = Latitude(['91d', '89d']) + lat = Latitude(["91d", "89d"]) with pytest.raises(ValueError): - lat = Latitude('-91d') + lat = Latitude("-91d") - lat = Latitude(['90d', '89d']) + lat = Latitude(["90d", "89d"]) # check that one can get items assert lat[0] == 90 * u.deg assert lat[1] == 89 * u.deg # and that comparison with angles works - assert np.all(lat == Angle(['90d', '89d'])) + assert np.all(lat == Angle(["90d", "89d"])) # check setitem works - lat[1] = 45. * u.deg - assert np.all(lat == Angle(['90d', '45d'])) + lat[1] = 45.0 * u.deg + assert np.all(lat == Angle(["90d", "45d"])) # but not with values out of range with pytest.raises(ValueError): lat[0] = 90.001 * u.deg with pytest.raises(ValueError): lat[0] = -90.001 * u.deg # these should also not destroy input (#1851) - assert np.all(lat == Angle(['90d', '45d'])) + assert np.all(lat == Angle(["90d", "45d"])) # conserve type on unit change (closes #1423) - angle = lat.to('radian') + angle = lat.to("radian") assert type(angle) is Latitude # but not on calculations angle = lat - 190 * u.deg assert type(angle) is Angle assert angle[0] == -100 * u.deg - lat = Latitude('80d') - angle = lat / 2. + lat = Latitude("80d") + angle = lat / 2.0 assert type(angle) is Angle assert angle == 40 * u.deg - angle = lat * 2. + angle = lat * 2.0 assert type(angle) is Angle assert angle == 160 * u.deg @@ -718,36 +731,38 @@ def test_latitude(): assert angle == -80 * u.deg # Test errors when trying to interoperate with longitudes. - with pytest.raises(TypeError) as excinfo: - lon = Longitude(10, 'deg') + with pytest.raises( + TypeError, match="A Latitude angle cannot be created from a Longitude angle" + ): + lon = Longitude(10, "deg") lat = Latitude(lon) - assert "A Latitude angle cannot be created from a Longitude angle" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - lon = Longitude(10, 'deg') - lat = Latitude([20], 'deg') + with pytest.raises( + TypeError, match="A Longitude angle cannot be assigned to a Latitude angle" + ): + lon = Longitude(10, "deg") + lat = Latitude([20], "deg") lat[0] = lon - assert "A Longitude angle cannot be assigned to a Latitude angle" in str(excinfo.value) # Check we can work around the Lat vs Long checks by casting explicitly to Angle. - lon = Longitude(10, 'deg') + lon = Longitude(10, "deg") lat = Latitude(Angle(lon)) assert lat.value == 10.0 # Check setitem. - lon = Longitude(10, 'deg') - lat = Latitude([20], 'deg') + lon = Longitude(10, "deg") + lat = Latitude([20], "deg") lat[0] = Angle(lon) assert lat.value[0] == 10.0 def test_longitude(): # Default wrapping at 360d with an array input - lon = Longitude(['370d', '88d']) - assert np.all(lon == Longitude(['10d', '88d'])) - assert np.all(lon == Angle(['10d', '88d'])) + lon = Longitude(["370d", "88d"]) + assert np.all(lon == Longitude(["10d", "88d"])) + assert np.all(lon == Angle(["10d", "88d"])) # conserve type on unit change and keep wrap_angle (closes #1423) - angle = lon.to('hourangle') + angle = lon.to("hourangle") assert type(angle) is Longitude assert angle.wrap_angle == lon.wrap_angle angle = lon[0] @@ -758,42 +773,44 @@ def test_longitude(): assert angle.wrap_angle == lon.wrap_angle # but not on calculations - angle = lon / 2. - assert np.all(angle == Angle(['5d', '44d'])) + angle = lon / 2.0 + assert np.all(angle == Angle(["5d", "44d"])) assert type(angle) is Angle - assert not hasattr(angle, 'wrap_angle') + assert not hasattr(angle, "wrap_angle") - angle = lon * 2. + 400 * u.deg - assert np.all(angle == Angle(['420d', '576d'])) + angle = lon * 2.0 + 400 * u.deg + assert np.all(angle == Angle(["420d", "576d"])) assert type(angle) is Angle # Test setting a mutable value and having it wrap lon[1] = -10 * u.deg - assert np.all(lon == Angle(['10d', '350d'])) + assert np.all(lon == Angle(["10d", "350d"])) # Test wrapping and try hitting some edge cases lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) - assert np.all(lon.degree == np.array([0., 90, 180, 270, 0])) + assert np.all(lon.degree == np.array([0.0, 90, 180, 270, 0])) - lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle='180d') - assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) + lon = Longitude( + np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle="180d" + ) + assert np.all(lon.degree == np.array([0.0, 90, -180, -90, 0])) # Wrap on setting wrap_angle property (also test auto-conversion of wrap_angle to an Angle) lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) - lon.wrap_angle = '180d' - assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) + lon.wrap_angle = "180d" + assert np.all(lon.degree == np.array([0.0, 90, -180, -90, 0])) - lon = Longitude('460d') - assert lon == Angle('100d') - lon.wrap_angle = '90d' - assert lon == Angle('-260d') + lon = Longitude("460d") + assert lon == Angle("100d") + lon.wrap_angle = "90d" + assert lon == Angle("-260d") # check that if we initialize a longitude with another longitude, # wrap_angle is kept by default lon2 = Longitude(lon) assert lon2.wrap_angle == lon.wrap_angle # but not if we explicitly set it - lon3 = Longitude(lon, wrap_angle='180d') + lon3 = Longitude(lon, wrap_angle="180d") assert lon3.wrap_angle == 180 * u.deg # check that wrap_angle is always an Angle @@ -802,7 +819,7 @@ def test_longitude(): assert lon.wrap_angle.__class__ is Angle # check that wrap_angle is not copied - wrap_angle=180 * u.deg + wrap_angle = 180 * u.deg lon = Longitude(lon, wrap_angle=wrap_angle) assert lon.wrap_angle == 180 * u.deg assert np.may_share_memory(lon.wrap_angle, wrap_angle) @@ -810,46 +827,52 @@ def test_longitude(): # check for problem reported in #2037 about Longitude initializing to -0 lon = Longitude(0, u.deg) lonstr = lon.to_string() - assert not lonstr.startswith('-') + assert not lonstr.startswith("-") # also make sure dtype is correctly conserved assert Longitude(0, u.deg, dtype=float).dtype == np.dtype(float) assert Longitude(0, u.deg, dtype=int).dtype == np.dtype(int) # Test errors when trying to interoperate with latitudes. - with pytest.raises(TypeError) as excinfo: - lat = Latitude(10, 'deg') + with pytest.raises( + TypeError, match="A Longitude angle cannot be created from a Latitude angle" + ): + lat = Latitude(10, "deg") lon = Longitude(lat) - assert "A Longitude angle cannot be created from a Latitude angle" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - lat = Latitude(10, 'deg') - lon = Longitude([20], 'deg') + with pytest.raises( + TypeError, match="A Latitude angle cannot be assigned to a Longitude angle" + ): + lat = Latitude(10, "deg") + lon = Longitude([20], "deg") lon[0] = lat - assert "A Latitude angle cannot be assigned to a Longitude angle" in str(excinfo.value) # Check we can work around the Lat vs Long checks by casting explicitly to Angle. - lat = Latitude(10, 'deg') + lat = Latitude(10, "deg") lon = Longitude(Angle(lat)) assert lon.value == 10.0 # Check setitem. - lat = Latitude(10, 'deg') - lon = Longitude([20], 'deg') + lat = Latitude(10, "deg") + lon = Longitude([20], "deg") lon[0] = Angle(lat) assert lon.value[0] == 10.0 def test_wrap_at(): a = Angle([-20, 150, 350, 360] * u.deg) - assert np.all(a.wrap_at(360 * u.deg).degree == np.array([340., 150., 350., 0.])) - assert np.all(a.wrap_at(Angle(360, unit=u.deg)).degree == np.array([340., 150., 350., 0.])) - assert np.all(a.wrap_at('360d').degree == np.array([340., 150., 350., 0.])) - assert np.all(a.wrap_at('180d').degree == np.array([-20., 150., -10., 0.])) - assert np.all(a.wrap_at(np.pi * u.rad).degree == np.array([-20., 150., -10., 0.])) + assert np.all(a.wrap_at(360 * u.deg).degree == np.array([340.0, 150.0, 350.0, 0.0])) + assert np.all( + a.wrap_at(Angle(360, unit=u.deg)).degree == np.array([340.0, 150.0, 350.0, 0.0]) + ) + assert np.all(a.wrap_at("360d").degree == np.array([340.0, 150.0, 350.0, 0.0])) + assert np.all(a.wrap_at("180d").degree == np.array([-20.0, 150.0, -10.0, 0.0])) + assert np.all( + a.wrap_at(np.pi * u.rad).degree == np.array([-20.0, 150.0, -10.0, 0.0]) + ) # Test wrapping a scalar Angle - a = Angle('190d') - assert a.wrap_at('180d') == Angle('-170d') + a = Angle("190d") + assert a.wrap_at("180d") == Angle("-170d") a = Angle(np.arange(-1000.0, 1000.0, 0.125), unit=u.deg) for wrap_angle in (270, 0.2, 0.0, 360.0, 500, -2000.125): @@ -864,18 +887,18 @@ def test_wrap_at(): def test_is_within_bounds(): a = Angle([-20, 150, 350] * u.deg) - assert a.is_within_bounds('0d', '360d') is False - assert a.is_within_bounds(None, '360d') is True + assert a.is_within_bounds("0d", "360d") is False + assert a.is_within_bounds(None, "360d") is True assert a.is_within_bounds(-30 * u.deg, None) is True - a = Angle('-20d') - assert a.is_within_bounds('0d', '360d') is False - assert a.is_within_bounds(None, '360d') is True + a = Angle("-20d") + assert a.is_within_bounds("0d", "360d") is False + assert a.is_within_bounds(None, "360d") is True assert a.is_within_bounds(-30 * u.deg, None) is True def test_angle_mismatched_unit(): - a = Angle('+6h7m8s', unit=u.degree) + a = Angle("+6h7m8s", unit=u.degree) assert_allclose(a.value, 91.78333333333332) @@ -884,23 +907,23 @@ def test_regression_formatting_negative(): # # >>> Angle(-1., unit='deg').to_string() # '-1d00m-0s' - assert Angle(-0., unit='deg').to_string() == '-0d00m00s' - assert Angle(-1., unit='deg').to_string() == '-1d00m00s' - assert Angle(-0., unit='hour').to_string() == '-0h00m00s' - assert Angle(-1., unit='hour').to_string() == '-1h00m00s' + assert Angle(-0.0, unit="deg").to_string() == "-0d00m00s" + assert Angle(-1.0, unit="deg").to_string() == "-1d00m00s" + assert Angle(-0.0, unit="hour").to_string() == "-0h00m00s" + assert Angle(-1.0, unit="hour").to_string() == "-1h00m00s" def test_regression_formatting_default_precision(): # Regression test for issue #11140 - assert Angle('10:20:30.12345678d').to_string() == '10d20m30.12345678s' - assert Angle('10d20m30.123456784564s').to_string() == '10d20m30.12345678s' - assert Angle('10d20m30.123s').to_string() == '10d20m30.123s' + assert Angle("10:20:30.12345678d").to_string() == "10d20m30.12345678s" + assert Angle("10d20m30.123456784564s").to_string() == "10d20m30.12345678s" + assert Angle("10d20m30.123s").to_string() == "10d20m30.123s" def test_empty_sep(): - a = Angle('05h04m31.93830s') + a = Angle("05h04m31.93830s") - assert a.to_string(sep='', precision=2, pad=True) == '050431.94' + assert a.to_string(sep="", precision=2, pad=True) == "050431.94" def test_create_tuple(): @@ -909,17 +932,17 @@ def test_create_tuple(): (d, m, s) tuples are not tested because of sign ambiguity issues (#13162) """ - with pytest.warns(AstropyDeprecationWarning, match='hms_to_hours'): + with pytest.warns(AstropyDeprecationWarning, match="hms_to_hours"): a1 = Angle((1, 30, 0), unit=u.hourangle) assert a1.value == 1.5 def test_list_of_quantities(): - a1 = Angle([1*u.deg, 1*u.hourangle]) + a1 = Angle([1 * u.deg, 1 * u.hourangle]) assert a1.unit == u.deg assert_allclose(a1.value, [1, 15]) - a2 = Angle([1*u.hourangle, 1*u.deg], u.deg) + a2 = Angle([1 * u.hourangle, 1 * u.deg], u.deg) assert a2.unit == u.deg assert_allclose(a2.value, [15, 1]) @@ -933,24 +956,24 @@ def test_multiply_divide(): assert a3.unit == (u.deg * u.deg) a3 = a1 / a2 - assert_allclose(a3.value, [.25, .4, .5]) + assert_allclose(a3.value, [0.25, 0.4, 0.5]) assert a3.unit == u.dimensionless_unscaled def test_mixed_string_and_quantity(): - a1 = Angle(['1d', 1. * u.deg]) - assert_array_equal(a1.value, [1., 1.]) + a1 = Angle(["1d", 1.0 * u.deg]) + assert_array_equal(a1.value, [1.0, 1.0]) assert a1.unit == u.deg - a2 = Angle(['1d', 1 * u.rad * np.pi, '3d']) - assert_array_equal(a2.value, [1., 180., 3.]) + a2 = Angle(["1d", 1 * u.rad * np.pi, "3d"]) + assert_array_equal(a2.value, [1.0, 180.0, 3.0]) assert a2.unit == u.deg def test_array_angle_tostring(): aobj = Angle([1, 2], u.deg) - assert aobj.to_string().dtype.kind == 'U' - assert np.all(aobj.to_string() == ['1d00m00s', '2d00m00s']) + assert aobj.to_string().dtype.kind == "U" + assert np.all(aobj.to_string() == ["1d00m00s", "2d00m00s"]) def test_wrap_at_without_new(): @@ -960,8 +983,8 @@ def test_wrap_at_without_new(): depend on array_finalize to set state. Longitude is used because the bug was in its _wrap_angle not getting initialized correctly """ - l1 = Longitude([1]*u.deg) - l2 = Longitude([2]*u.deg) + l1 = Longitude([1] * u.deg) + l2 = Longitude([2] * u.deg) l = np.concatenate([l1, l2]) assert l._wrap_angle is not None @@ -973,19 +996,19 @@ def test__str__(): """ # scalar angle - scangle = Angle('10.2345d') + scangle = Angle("10.2345d") strscangle = scangle.__str__() - assert strscangle == '10d14m04.2s' + assert strscangle == "10d14m04.2s" # non-scalar array angles - arrangle = Angle(['10.2345d', '-20d']) + arrangle = Angle(["10.2345d", "-20d"]) strarrangle = arrangle.__str__() - assert strarrangle == '[10d14m04.2s -20d00m00s]' + assert strarrangle == "[10d14m04.2s -20d00m00s]" # summarizing for large arrays, ... should appear bigarrangle = Angle(np.ones(10000), u.deg) - assert '...' in bigarrangle.__str__() + assert "..." in bigarrangle.__str__() def test_repr_latex(): @@ -1001,12 +1024,12 @@ def test_repr_latex(): arrangle = Angle([1, 2.1], u.deg) rlarrangle = arrangle._repr_latex_() - assert rlscangle == r'$2^\circ06{}^\prime00{}^{\prime\prime}$' - assert rlscangle.split('$')[1] in rlarrangle + assert rlscangle == r"$2^\circ06{}^\prime00{}^{\prime\prime}$" + assert rlscangle.split("$")[1] in rlarrangle # make sure the ... appears for large arrays - bigarrangle = Angle(np.ones(50000)/50000., u.deg) - assert '...' in bigarrangle._repr_latex_() + bigarrangle = Angle(np.ones(50000) / 50000.0, u.deg) + assert "..." in bigarrangle._repr_latex_() def test_angle_with_cds_units_enabled(): @@ -1018,11 +1041,12 @@ def test_angle_with_cds_units_enabled(): # the problem is with the parser, so remove it temporarily from astropy.coordinates.angle_formats import _AngleParser from astropy.units import cds + del _AngleParser._thread_local._parser with cds.enable(): - Angle('5d') + Angle("5d") del _AngleParser._thread_local._parser - Angle('5d') + Angle("5d") def test_longitude_nan(): @@ -1039,59 +1063,44 @@ def test_angle_wrap_at_nan(): # Check that no attempt is made to wrap a NaN angle angle = Angle([0, np.nan, 1] * u.deg) angle.flags.writeable = False # to force an error if a write is attempted - angle.wrap_at(180*u.deg, inplace=True) + angle.wrap_at(180 * u.deg, inplace=True) def test_angle_multithreading(): """ Regression test for issue #7168 """ - angles = ['00:00:00']*10000 + angles = ["00:00:00"] * 10000 def parse_test(i=0): - Angle(angles, unit='hour') + Angle(angles, unit="hour") + for i in range(10): threading.Thread(target=parse_test, args=(i,)).start() @pytest.mark.parametrize("cls", [Angle, Longitude, Latitude]) -@pytest.mark.parametrize("input, expstr, exprepr", - [(np.nan*u.deg, - "nan", - "nan deg"), - ([np.nan, 5, 0]*u.deg, - "[nan 5d00m00s 0d00m00s]", - "[nan, 5., 0.] deg"), - ([6, np.nan, 0]*u.deg, - "[6d00m00s nan 0d00m00s]", - "[6., nan, 0.] deg"), - ([np.nan, np.nan, np.nan]*u.deg, - "[nan nan nan]", - "[nan, nan, nan] deg"), - (np.nan*u.hour, - "nan", - "nan hourangle"), - ([np.nan, 5, 0]*u.hour, - "[nan 5h00m00s 0h00m00s]", - "[nan, 5., 0.] hourangle"), - ([6, np.nan, 0]*u.hour, - "[6h00m00s nan 0h00m00s]", - "[6., nan, 0.] hourangle"), - ([np.nan, np.nan, np.nan]*u.hour, - "[nan nan nan]", - "[nan, nan, nan] hourangle"), - (np.nan*u.rad, - "nan", - "nan rad"), - ([np.nan, 1, 0]*u.rad, - "[nan 1rad 0rad]", - "[nan, 1., 0.] rad"), - ([1.50, np.nan, 0]*u.rad, - "[1.5rad nan 0rad]", - "[1.5, nan, 0.] rad"), - ([np.nan, np.nan, np.nan]*u.rad, - "[nan nan nan]", - "[nan, nan, nan] rad")]) +@pytest.mark.parametrize( + "input, expstr, exprepr", + [ + (np.nan * u.deg, "nan", "nan deg"), + ([np.nan, 5, 0] * u.deg, "[nan 5d00m00s 0d00m00s]", "[nan, 5., 0.] deg"), + ([6, np.nan, 0] * u.deg, "[6d00m00s nan 0d00m00s]", "[6., nan, 0.] deg"), + ([np.nan, np.nan, np.nan] * u.deg, "[nan nan nan]", "[nan, nan, nan] deg"), + (np.nan * u.hour, "nan", "nan hourangle"), + ([np.nan, 5, 0] * u.hour, "[nan 5h00m00s 0h00m00s]", "[nan, 5., 0.] hourangle"), + ([6, np.nan, 0] * u.hour, "[6h00m00s nan 0h00m00s]", "[6., nan, 0.] hourangle"), + ( + [np.nan, np.nan, np.nan] * u.hour, + "[nan nan nan]", + "[nan, nan, nan] hourangle", + ), + (np.nan * u.rad, "nan", "nan rad"), + ([np.nan, 1, 0] * u.rad, "[nan 1rad 0rad]", "[nan, 1., 0.] rad"), + ([1.50, np.nan, 0] * u.rad, "[1.5rad nan 0rad]", "[1.5, nan, 0.] rad"), + ([np.nan, np.nan, np.nan] * u.rad, "[nan nan nan]", "[nan, nan, nan] rad"), + ], +) def test_str_repr_angles_nan(cls, input, expstr, exprepr): """ Regression test for issue #11473 @@ -1100,7 +1109,7 @@ def test_str_repr_angles_nan(cls, input, expstr, exprepr): assert str(q) == expstr # Deleting whitespaces since repr appears to be adding them for some values # making the test fail. - assert repr(q).replace(" ", "") == f'<{cls.__name__}{exprepr}>'.replace(" ","") + assert repr(q).replace(" ", "") == f"<{cls.__name__}{exprepr}>".replace(" ", "") @pytest.mark.parametrize("sign", (-1, 1)) @@ -1115,7 +1124,7 @@ def test_str_repr_angles_nan(cls, input, expstr, exprepr): # making validate have side effects, so it's not implemented for now # (np.float32(np.pi / 2), np.pi / 2, np.float64, np.float64), # (np.float32(-np.pi / 2), -np.pi / 2, np.float64, np.float64), - ] + ], ) def test_latitude_limits(value, expected_value, dtype, expected_dtype, sign): """ @@ -1143,7 +1152,7 @@ def test_latitude_limits(value, expected_value, dtype, expected_dtype, sign): (0.50001 * np.pi, np.float32), (np.float32(0.50001 * np.pi), np.float32), (0.50001 * np.pi, np.float64), - ] + ], ) def test_latitude_out_of_limits(value, dtype): """ diff --git a/astropy/coordinates/tests/test_angular_separation.py b/astropy/coordinates/tests/test_angular_separation.py index 9aa7bef06d2..4fa301be2e3 100644 --- a/astropy/coordinates/tests/test_angular_separation.py +++ b/astropy/coordinates/tests/test_angular_separation.py @@ -14,21 +14,22 @@ from astropy.tests.helper import assert_quantity_allclose as assert_allclose # lon1, lat1, lon2, lat2 in degrees -coords = [(1, 0, 0, 0), - (0, 1, 0, 0), - (0, 0, 1, 0), - (0, 0, 0, 1), - (0, 0, 10, 0), - (0, 0, 90, 0), - (0, 0, 180, 0), - (0, 45, 0, -45), - (0, 60, 0, -30), - (-135, -15, 45, 15), - (100, -89, -80, 89), - (0, 0, 0, 0), - (0, 0, 1. / 60., 1. / 60.)] -correct_seps = [1, 1, 1, 1, 10, 90, 180, 90, 90, 180, 180, 0, - 0.023570225877234643] +coords = [ + (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1), + (0, 0, 10, 0), + (0, 0, 90, 0), + (0, 0, 180, 0), + (0, 45, 0, -45), + (0, 60, 0, -30), + (-135, -15, 45, 15), + (100, -89, -80, 89), + (0, 0, 0, 0), + (0, 0, 1.0 / 60.0, 1.0 / 60.0), +] +correct_seps = [1, 1, 1, 1, 10, 90, 180, 90, 90, 180, 180, 0, 0.023570225877234643] correctness_margin = 2e-10 @@ -39,12 +40,9 @@ def test_angsep(): from astropy.coordinates.angle_utilities import angular_separation # check it both works with floats in radians, Quantities, or Angles - for conv in (np.deg2rad, - lambda x: u.Quantity(x, "deg"), - lambda x: Angle(x, "deg")): + for conv in (np.deg2rad, lambda x: u.Quantity(x, "deg"), lambda x: Angle(x, "deg")): for (lon1, lat1, lon2, lat2), corrsep in zip(coords, correct_seps): - angsep = angular_separation(conv(lon1), conv(lat1), - conv(lon2), conv(lat2)) + angsep = angular_separation(conv(lon1), conv(lat1), conv(lon2), conv(lat2)) assert np.fabs(angsep - conv(corrsep)) < conv(correctness_margin) @@ -54,8 +52,8 @@ def test_fk5_seps(): This is a regression test for github issue #891 """ - a = FK5(1.*u.deg, 1.*u.deg) - b = FK5(2.*u.deg, 2.*u.deg) + a = FK5(1.0 * u.deg, 1.0 * u.deg) + b = FK5(2.0 * u.deg, 2.0 * u.deg) a.separation(b) @@ -63,15 +61,15 @@ def test_proj_separations(): """ Test angular separation functionality """ - c1 = ICRS(ra=0*u.deg, dec=0*u.deg) - c2 = ICRS(ra=0*u.deg, dec=1*u.deg) + c1 = ICRS(ra=0 * u.deg, dec=0 * u.deg) + c2 = ICRS(ra=0 * u.deg, dec=1 * u.deg) sep = c2.separation(c1) # returns an Angle object assert isinstance(sep, Angle) - assert_allclose(sep.degree, 1.) - assert_allclose(sep.arcminute, 60.) + assert_allclose(sep.degree, 1.0) + assert_allclose(sep.arcminute, 60.0) # these operations have ambiguous interpretations for points on a sphere with pytest.raises(TypeError): @@ -79,27 +77,27 @@ def test_proj_separations(): with pytest.raises(TypeError): c1 - c2 - ngp = Galactic(l=0*u.degree, b=90*u.degree) - ncp = ICRS(ra=0*u.degree, dec=90*u.degree) + ngp = Galactic(l=0 * u.degree, b=90 * u.degree) + ncp = ICRS(ra=0 * u.degree, dec=90 * u.degree) # if there is a defined conversion between the relevant coordinate systems, # it will be automatically performed to get the right angular separation - assert_allclose(ncp.separation(ngp.transform_to(ICRS())).degree, - ncp.separation(ngp).degree) + assert_allclose( + ncp.separation(ngp.transform_to(ICRS())).degree, ncp.separation(ngp).degree + ) # distance from the north galactic pole to celestial pole - assert_allclose(ncp.separation(ngp.transform_to(ICRS())).degree, - 62.87174758503201) + assert_allclose(ncp.separation(ngp.transform_to(ICRS())).degree, 62.87174758503201) def test_3d_separations(): """ Test 3D separation functionality """ - c1 = ICRS(ra=1*u.deg, dec=1*u.deg, distance=9*u.kpc) - c2 = ICRS(ra=1*u.deg, dec=1*u.deg, distance=10*u.kpc) + c1 = ICRS(ra=1 * u.deg, dec=1 * u.deg, distance=9 * u.kpc) + c2 = ICRS(ra=1 * u.deg, dec=1 * u.deg, distance=10 * u.kpc) sep3d = c2.separation_3d(c1) assert isinstance(sep3d, Distance) - assert_allclose(sep3d - 1*u.kpc, 0*u.kpc, atol=1e-12*u.kpc) + assert_allclose(sep3d - 1 * u.kpc, 0 * u.kpc, atol=1e-12 * u.kpc) diff --git a/astropy/coordinates/tests/test_api_ape5.py b/astropy/coordinates/tests/test_api_ape5.py index 4dc525e510a..b5c27f85d18 100644 --- a/astropy/coordinates/tests/test_api_ape5.py +++ b/astropy/coordinates/tests/test_api_ape5.py @@ -38,61 +38,77 @@ def test_representations_api(): # length-0 "arrays") # They can be initialized with a variety of ways that make intuitive sense. # Distance is optional. - UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg) - UnitSphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg) - SphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg, distance=10*u.kpc) + UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) + UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) + SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) # In the initial implementation, the lat/lon/distance arguments to the # initializer must be in order. A *possible* future change will be to allow # smarter guessing of the order. E.g. `Latitude` and `Longitude` objects can be # given in any order. UnitSphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg)) - SphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg), Distance(10, u.kpc)) + SphericalRepresentation( + Longitude(8, u.hour), Latitude(5, u.deg), Distance(10, u.kpc) + ) # Arrays of any of the inputs are fine - UnitSphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg) + UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg) # Default is to copy arrays, but optionally, it can be a reference - UnitSphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, copy=False) + UnitSphericalRepresentation( + lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, copy=False + ) # strings are parsed by `Latitude` and `Longitude` constructors, so no need to # implement parsing in the Representation classes - UnitSphericalRepresentation(lon=Angle('2h6m3.3s'), lat=Angle('0.1rad')) + UnitSphericalRepresentation(lon=Angle("2h6m3.3s"), lat=Angle("0.1rad")) # Or, you can give `Quantity`s with keywords, and they will be internally # converted to Angle/Distance - c1 = SphericalRepresentation(lon=8*u.hourangle, lat=5*u.deg, distance=10*u.kpc) + c1 = SphericalRepresentation( + lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc + ) # Can also give another representation object with the `reprobj` keyword. c2 = SphericalRepresentation.from_representation(c1) # distance, lat, and lon typically will just match in shape - SphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, distance=[10, 11]*u.kpc) + SphericalRepresentation( + lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=[10, 11] * u.kpc + ) # if the inputs are not the same, if possible they will be broadcast following # numpy's standard broadcasting rules. - c2 = SphericalRepresentation(lon=[8, 9]*u.hourangle, lat=[5, 6]*u.deg, distance=10*u.kpc) + c2 = SphericalRepresentation( + lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=10 * u.kpc + ) assert len(c2.distance) == 2 # when they can't be broadcast, it is a ValueError (same as Numpy) with pytest.raises(ValueError): - c2 = UnitSphericalRepresentation(lon=[8, 9, 10]*u.hourangle, lat=[5, 6]*u.deg) + c2 = UnitSphericalRepresentation( + lon=[8, 9, 10] * u.hourangle, lat=[5, 6] * u.deg + ) # It's also possible to pass in scalar quantity lists with mixed units. These # are converted to array quantities following the same rule as `Quantity`: all # elements are converted to match the first element's units. - c2 = UnitSphericalRepresentation(lon=Angle([8*u.hourangle, 135*u.deg]), - lat=Angle([5*u.deg, (6*np.pi/180)*u.rad])) + c2 = UnitSphericalRepresentation( + lon=Angle([8 * u.hourangle, 135 * u.deg]), + lat=Angle([5 * u.deg, (6 * np.pi / 180) * u.rad]), + ) assert c2.lat.unit == u.deg and c2.lon.unit == u.hourangle npt.assert_almost_equal(c2.lon[1].value, 9) # The Quantity initializer itself can also be used to force the unit even if the # first element doesn't have the right unit - lon = u.Quantity([120*u.deg, 135*u.deg], u.hourangle) - lat = u.Quantity([(5*np.pi/180)*u.rad, 0.4*u.hourangle], u.deg) + lon = u.Quantity([120 * u.deg, 135 * u.deg], u.hourangle) + lat = u.Quantity([(5 * np.pi / 180) * u.rad, 0.4 * u.hourangle], u.deg) c2 = UnitSphericalRepresentation(lon, lat) # regardless of how input, the `lat` and `lon` come out as angle/distance assert isinstance(c1.lat, Angle) - assert isinstance(c1.lat, Latitude) # `Latitude` is an `~astropy.coordinates.Angle` subclass + assert isinstance( + c1.lat, Latitude + ) # `Latitude` is an `~astropy.coordinates.Angle` subclass assert isinstance(c1.distance, Distance) # but they are read-only, as representations are immutable once created @@ -106,7 +122,7 @@ def test_representations_api(): # coordinates are defined, other conventions can be included as new classes. # Later there may be other conventions that we implement - for now just the # physics convention, as it is one of the most common cases. - _ = PhysicsSphericalRepresentation(phi=120*u.deg, theta=85*u.deg, r=3*u.kpc) + _ = PhysicsSphericalRepresentation(phi=120 * u.deg, theta=85 * u.deg, r=3 * u.kpc) # first dimension must be length-3 if a lone `Quantity` is passed in. c1 = CartesianRepresentation(np.random.randn(3, 100) * u.kpc) @@ -116,23 +132,25 @@ def test_representations_api(): assert c1.y.shape[0] == 100 assert c1.z.shape[0] == 100 # can also give each as separate keywords - CartesianRepresentation(x=np.random.randn(100)*u.kpc, - y=np.random.randn(100)*u.kpc, - z=np.random.randn(100)*u.kpc) + CartesianRepresentation( + x=np.random.randn(100) * u.kpc, + y=np.random.randn(100) * u.kpc, + z=np.random.randn(100) * u.kpc, + ) # if the units don't match but are all distances, they will automatically be # converted to match `x` xarr, yarr, zarr = np.random.randn(3, 100) - c1 = CartesianRepresentation(x=xarr*u.kpc, y=yarr*u.kpc, z=zarr*u.kpc) - c2 = CartesianRepresentation(x=xarr*u.kpc, y=yarr*u.kpc, z=zarr*u.pc) + c1 = CartesianRepresentation(x=xarr * u.kpc, y=yarr * u.kpc, z=zarr * u.kpc) + c2 = CartesianRepresentation(x=xarr * u.kpc, y=yarr * u.kpc, z=zarr * u.pc) assert c1.xyz.unit == c2.xyz.unit == u.kpc - assert_allclose((c1.z / 1000) - c2.z, 0*u.kpc, atol=1e-10*u.kpc) + assert_allclose((c1.z / 1000) - c2.z, 0 * u.kpc, atol=1e-10 * u.kpc) # representations convert into other representations via `represent_as` - srep = SphericalRepresentation(lon=90*u.deg, lat=0*u.deg, distance=1*u.pc) + srep = SphericalRepresentation(lon=90 * u.deg, lat=0 * u.deg, distance=1 * u.pc) crep = srep.represent_as(CartesianRepresentation) - assert_allclose(crep.x, 0*u.pc, atol=1e-10*u.pc) - assert_allclose(crep.y, 1*u.pc, atol=1e-10*u.pc) - assert_allclose(crep.z, 0*u.pc, atol=1e-10*u.pc) + assert_allclose(crep.x, 0 * u.pc, atol=1e-10 * u.pc) + assert_allclose(crep.y, 1 * u.pc, atol=1e-10 * u.pc) + assert_allclose(crep.z, 0 * u.pc, atol=1e-10 * u.pc) # The functions that actually do the conversion are defined via methods on the # representation classes. This may later be expanded into a full registerable # transform graph like the coordinate frames, but initially it will be a simpler @@ -151,32 +169,34 @@ def test_frame_api(): # frames and they *may* also contain data as one of the representation objects, # in which case they are the actual coordinate objects themselves. # They can always accept a representation as a first argument - icrs = ICRS(UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg)) + icrs = ICRS(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)) # which is stored as the `data` attribute - assert icrs.data.lat == 5*u.deg - assert icrs.data.lon == 8*u.hourangle + assert icrs.data.lat == 5 * u.deg + assert icrs.data.lon == 8 * u.hourangle # Frames that require additional information like equinoxs or obstimes get them # as keyword parameters to the frame constructor. Where sensible, defaults are # used. E.g., FK5 is almost always J2000 equinox - fk5 = FK5(UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg)) - J2000 = time.Time('J2000') - fk5_2000 = FK5(UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg), equinox=J2000) + fk5 = FK5(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)) + J2000 = time.Time("J2000") + fk5_2000 = FK5( + UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg), equinox=J2000 + ) assert fk5.equinox == fk5_2000.equinox # the information required to specify the frame is immutable - J2001 = time.Time('J2001') + J2001 = time.Time("J2001") with pytest.raises(AttributeError): fk5.equinox = J2001 # Similar for the representation data. with pytest.raises(AttributeError): - fk5.data = UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg) + fk5.data = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) # There is also a class-level attribute that lists the attributes needed to # identify the frame. These include attributes like `equinox` shown above. - assert all(nm in ('equinox', 'obstime') for nm in fk5.frame_attributes) + assert all(nm in ("equinox", "obstime") for nm in fk5.frame_attributes) # the `frame_attributes` are called for particularly in the # high-level class (discussed below) to allow round-tripping between various @@ -184,9 +204,9 @@ def test_frame_api(): # advanced users' use. # The actual position information is accessed via the representation objects - assert_allclose(icrs.represent_as(SphericalRepresentation).lat, 5*u.deg) + assert_allclose(icrs.represent_as(SphericalRepresentation).lat, 5 * u.deg) # shorthand for the above - assert_allclose(icrs.spherical.lat, 5*u.deg) + assert_allclose(icrs.spherical.lat, 5 * u.deg) assert icrs.cartesian.z.value > 0 # Many frames have a "default" representation, the one in which they are @@ -194,14 +214,14 @@ def test_frame_api(): # coordinates. E.g., most equatorial coordinate systems are spherical with RA and # Dec. This works simply as a shorthand for the longer form above - assert_allclose(icrs.dec, 5*u.deg) - assert_allclose(fk5.ra, 8*u.hourangle) + assert_allclose(icrs.dec, 5 * u.deg) + assert_allclose(fk5.ra, 8 * u.hourangle) assert icrs.representation_type == SphericalRepresentation # low-level classes can also be initialized with names valid for that representation # and frame: - icrs_2 = ICRS(ra=8*u.hour, dec=5*u.deg, distance=1*u.kpc) + icrs_2 = ICRS(ra=8 * u.hour, dec=5 * u.deg, distance=1 * u.kpc) assert_allclose(icrs.ra, icrs_2.ra) # and these are taken as the default if keywords are not given: @@ -210,14 +230,14 @@ def test_frame_api(): # they also are capable of computing on-sky or 3d separations from each other, # which will be a direct port of the existing methods: - coo1 = ICRS(ra=0*u.hour, dec=0*u.deg) - coo2 = ICRS(ra=0*u.hour, dec=1*u.deg) + coo1 = ICRS(ra=0 * u.hour, dec=0 * u.deg) + coo2 = ICRS(ra=0 * u.hour, dec=1 * u.deg) # `separation` is the on-sky separation assert_allclose(coo1.separation(coo2).degree, 1.0) # while `separation_3d` includes the 3D distance information - coo3 = ICRS(ra=0*u.hour, dec=0*u.deg, distance=1*u.kpc) - coo4 = ICRS(ra=0*u.hour, dec=0*u.deg, distance=2*u.kpc) + coo3 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=1 * u.kpc) + coo4 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=2 * u.kpc) assert coo3.separation_3d(coo4).kpc == 1.0 # The next example fails because `coo1` and `coo2` don't have distances @@ -238,11 +258,11 @@ def test_transform_api(): # Transformation functionality is the key to the whole scheme: they transform # low-level classes from one frame to another. # (used below but defined above in the API) - fk5 = FK5(ra=8*u.hour, dec=5*u.deg) + fk5 = FK5(ra=8 * u.hour, dec=5 * u.deg) # If no data (or `None`) is given, the class acts as a specifier of a frame, but # without any stored data. - J2001 = time.Time('J2001') + J2001 = time.Time("J2001") fk5_J2001_frame = FK5(equinox=J2001) # if they do not have data, the string instead is the frame specification @@ -251,7 +271,7 @@ def test_transform_api(): # Note that, although a frame object is immutable and can't have data added, it # can be used to create a new object that does have data by giving the # `realize_frame` method a representation: - srep = UnitSphericalRepresentation(lon=8*u.hour, lat=5*u.deg) + srep = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) fk5_j2001_with_data = fk5_J2001_frame.realize_frame(srep) assert fk5_j2001_with_data.data is not None # Now `fk5_j2001_with_data` is in the same frame as `fk5_J2001_frame`, but it @@ -266,17 +286,17 @@ def test_transform_api(): # transforming to a new frame necessarily loses framespec information if that # information is not applicable to the new frame. This means transforms are not # always round-trippable: - fk5_2 = FK5(ra=8*u.hour, dec=5*u.deg, equinox=J2001) + fk5_2 = FK5(ra=8 * u.hour, dec=5 * u.deg, equinox=J2001) ic_trans = fk5_2.transform_to(ICRS()) # `ic_trans` does not have an `equinox`, so now when we transform back to FK5, # it's a *different* RA and Dec fk5_trans = ic_trans.transform_to(FK5()) - assert not allclose(fk5_2.ra, fk5_trans.ra, rtol=0, atol=1e-10*u.deg) + assert not allclose(fk5_2.ra, fk5_trans.ra, rtol=0, atol=1e-10 * u.deg) # But if you explicitly give the right equinox, all is fine fk5_trans_2 = fk5_2.transform_to(FK5(equinox=J2001)) - assert_allclose(fk5_2.ra, fk5_trans_2.ra, rtol=0, atol=1e-10*u.deg) + assert_allclose(fk5_2.ra, fk5_trans_2.ra, rtol=0, atol=1e-10 * u.deg) # Trying to transforming a frame with no data is of course an error: with pytest.raises(ValueError): @@ -309,7 +329,7 @@ def new_to_fk5(newobj, fk5frame): def test_highlevel_api(): - J2001 = time.Time('J2001') + J2001 = time.Time("J2001") # <--------------------------"High-level" class--------------------------------> # The "high-level" class is intended to wrap the lower-level classes in such a @@ -321,16 +341,20 @@ def test_highlevel_api(): # this creates an object that contains an `ICRS` low-level class, initialized # identically to the first ICRS example further up. - sc = coords.SkyCoord(coords.SphericalRepresentation(lon=8 * u.hour, - lat=5 * u.deg, distance=1 * u.kpc), frame='icrs') + sc = coords.SkyCoord( + coords.SphericalRepresentation( + lon=8 * u.hour, lat=5 * u.deg, distance=1 * u.kpc + ), + frame="icrs", + ) # Other representations and `system` keywords delegate to the appropriate # low-level class. The already-existing registry for user-defined coordinates # will be used by `SkyCoordinate` to figure out what various the `system` # keyword actually means. - sc = coords.SkyCoord(ra=8 * u.hour, dec=5 * u.deg, frame='icrs') - sc = coords.SkyCoord(l=120 * u.deg, b=5 * u.deg, frame='galactic') + sc = coords.SkyCoord(ra=8 * u.hour, dec=5 * u.deg, frame="icrs") + sc = coords.SkyCoord(l=120 * u.deg, b=5 * u.deg, frame="galactic") # High-level classes can also be initialized directly from low-level objects sc = coords.SkyCoord(coords.ICRS(ra=8 * u.hour, dec=5 * u.deg)) @@ -346,7 +370,7 @@ def test_highlevel_api(): # funny ways # assert repr(sc.frame) == '' rscf = repr(sc.frame) - assert rscf.startswith('' rsc = repr(sc) - assert rsc.startswith('' if HAS_SCIPY: - cat1 = coords.SkyCoord(ra=[1, 2]*u.hr, dec=[3, 4.01]*u.deg, - distance=[5, 6]*u.kpc, frame='icrs') - cat2 = coords.SkyCoord(ra=[1, 2, 2.01]*u.hr, dec=[3, 4, 5]*u.deg, - distance=[5, 200, 6]*u.kpc, frame='icrs') + cat1 = coords.SkyCoord( + ra=[1, 2] * u.hr, + dec=[3, 4.01] * u.deg, + distance=[5, 6] * u.kpc, + frame="icrs", + ) + cat2 = coords.SkyCoord( + ra=[1, 2, 2.01] * u.hr, + dec=[3, 4, 5] * u.deg, + distance=[5, 200, 6] * u.kpc, + frame="icrs", + ) idx1, sep2d1, dist3d1 = cat1.match_to_catalog_sky(cat2) idx2, sep2d2, dist3d2 = cat1.match_to_catalog_3d(cat2) @@ -422,19 +454,19 @@ def test_highlevel_api(): @pytest.mark.remote_data def test_highlevel_api_remote(): - m31icrs = coords.SkyCoord.from_name('M31', frame='icrs') + m31icrs = coords.SkyCoord.from_name("M31", frame="icrs") m31str = str(m31icrs) - assert m31str.startswith('') - assert '10.68' in m31str - assert '41.26' in m31str + assert m31str.startswith("") + assert "10.68" in m31str + assert "41.26" in m31str # The above is essentially a replacement of the below, but tweaked so that # small/moderate changes in what `from_name` returns don't cause the tests # to fail # assert str(m31icrs) == '' - m31fk4 = coords.SkyCoord.from_name('M31', frame='fk4') + m31fk4 = coords.SkyCoord.from_name("M31", frame="fk4") assert not m31icrs.is_equivalent_frame(m31fk4) - assert np.abs(m31icrs.ra - m31fk4.ra) > .5*u.deg + assert np.abs(m31icrs.ra - m31fk4.ra) > 0.5 * u.deg diff --git a/astropy/coordinates/tests/test_arrays.py b/astropy/coordinates/tests/test_arrays.py index 87071632619..16e08ac2819 100644 --- a/astropy/coordinates/tests/test_arrays.py +++ b/astropy/coordinates/tests/test_arrays.py @@ -26,16 +26,14 @@ def test_angle_arrays(): Test arrays values with Angle objects. """ # Tests incomplete - a1 = Angle([0, 45, 90, 180, 270, 360, 720.], unit=u.degree) - npt.assert_almost_equal([0., 45., 90., 180., 270., 360., 720.], a1.value) + a1 = Angle([0, 45, 90, 180, 270, 360, 720.0], unit=u.degree) + npt.assert_almost_equal([0.0, 45.0, 90.0, 180.0, 270.0, 360.0, 720.0], a1.value) a2 = Angle(np.array([-90, -45, 0, 45, 90, 180, 270, 360]), unit=u.degree) - npt.assert_almost_equal([-90, -45, 0, 45, 90, 180, 270, 360], - a2.value) + npt.assert_almost_equal([-90, -45, 0, 45, 90, 180, 270, 360], a2.value) a3 = Angle(["12 degrees", "3 hours", "5 deg", "4rad"]) - npt.assert_almost_equal([12., 45., 5., 229.18311805], - a3.value) + npt.assert_almost_equal([12.0, 45.0, 5.0, 229.18311805], a3.value) assert a3.unit == u.degree a4 = Angle(["12 degrees", "3 hours", "5 deg", "4rad"], u.radian) @@ -51,8 +49,10 @@ def test_angle_arrays(): if NUMPY_LT_1_24: stack.enter_context(pytest.raises(TypeError)) stack.enter_context( - pytest.warns(DeprecationWarning, - match='automatic object dtype is deprecated')) + pytest.warns( + DeprecationWarning, match="automatic object dtype is deprecated" + ) + ) else: stack.enter_context(pytest.raises(ValueError)) @@ -84,10 +84,10 @@ def test_hms(): npt.assert_almost_equal(s, [0, 0, -0]) hms = a1.hms - hours = hms[0] + hms[1] / 60. + hms[2] / 3600. + hours = hms[0] + hms[1] / 60.0 + hms[2] / 3600.0 npt.assert_almost_equal(a1.hour, hours) - with pytest.warns(AstropyDeprecationWarning, match='hms_to_hours'): + with pytest.warns(AstropyDeprecationWarning, match="hms_to_hours"): a2 = Angle(hms, unit=u.hour) npt.assert_almost_equal(a2.radian, a1.radian) @@ -97,26 +97,28 @@ def test_array_coordinates_creation(): """ Test creating coordinates from arrays. """ - c = ICRS(np.array([1, 2])*u.deg, np.array([3, 4])*u.deg) + c = ICRS(np.array([1, 2]) * u.deg, np.array([3, 4]) * u.deg) assert not c.ra.isscalar with pytest.raises(ValueError): - c = ICRS(np.array([1, 2])*u.deg, np.array([3, 4, 5])*u.deg) + c = ICRS(np.array([1, 2]) * u.deg, np.array([3, 4, 5]) * u.deg) with pytest.raises(ValueError): - c = ICRS(np.array([1, 2, 4, 5])*u.deg, np.array([[3, 4], [5, 6]])*u.deg) + c = ICRS(np.array([1, 2, 4, 5]) * u.deg, np.array([[3, 4], [5, 6]]) * u.deg) # make sure cartesian initialization also works - cart = CartesianRepresentation(x=[1., 2.]*u.kpc, y=[3., 4.]*u.kpc, z=[5., 6.]*u.kpc) + cart = CartesianRepresentation( + x=[1.0, 2.0] * u.kpc, y=[3.0, 4.0] * u.kpc, z=[5.0, 6.0] * u.kpc + ) c = ICRS(cart) # also ensure strings can be arrays - c = SkyCoord(['1d0m0s', '2h02m00.3s'], ['3d', '4d']) + c = SkyCoord(["1d0m0s", "2h02m00.3s"], ["3d", "4d"]) # but invalid strings cannot with pytest.raises(ValueError): - c = SkyCoord(Angle(['10m0s', '2h02m00.3s']), Angle(['3d', '4d'])) + c = SkyCoord(Angle(["10m0s", "2h02m00.3s"]), Angle(["3d", "4d"])) with pytest.raises(ValueError): - c = SkyCoord(Angle(['1d0m0s', '2h02m00.3s']), Angle(['3x', '4d'])) + c = SkyCoord(Angle(["1d0m0s", "2h02m00.3s"]), Angle(["3x", "4d"])) def test_array_coordinates_distances(): @@ -124,17 +126,31 @@ def test_array_coordinates_distances(): Test creating coordinates from arrays and distances. """ # correct way - ICRS(ra=np.array([1, 2])*u.deg, dec=np.array([3, 4])*u.deg, distance=[.1, .2] * u.kpc) + ICRS( + ra=np.array([1, 2]) * u.deg, + dec=np.array([3, 4]) * u.deg, + distance=[0.1, 0.2] * u.kpc, + ) with pytest.raises(ValueError): # scalar distance and mismatched array coordinates - ICRS(ra=np.array([1, 2, 3])*u.deg, dec=np.array([[3, 4], [5, 6]])*u.deg, distance=2. * u.kpc) + ICRS( + ra=np.array([1, 2, 3]) * u.deg, + dec=np.array([[3, 4], [5, 6]]) * u.deg, + distance=2.0 * u.kpc, + ) with pytest.raises(ValueError): # more distance values than coordinates - ICRS(ra=np.array([1, 2])*u.deg, dec=np.array([3, 4])*u.deg, distance=[.1, .2, 3.] * u.kpc) + ICRS( + ra=np.array([1, 2]) * u.deg, + dec=np.array([3, 4]) * u.deg, + distance=[0.1, 0.2, 3.0] * u.kpc, + ) -@pytest.mark.parametrize(('arrshape', 'distance'), [((2, ), None), ((4, 2, 5), None), ((4, 2, 5), 2 * u.kpc)]) +@pytest.mark.parametrize( + ("arrshape", "distance"), [((2,), None), ((4, 2, 5), None), ((4, 2, 5), 2 * u.kpc)] +) def test_array_coordinates_transformations(arrshape, distance): """ Test transformation on coordinates with array content (first length-2 1D, then a 3D array) @@ -146,7 +162,7 @@ def test_array_coordinates_transformations(arrshape, distance): distance = np.ones(arrshape) * distance print(raarr, decarr, distance) - c = ICRS(ra=raarr*u.deg, dec=decarr*u.deg, distance=distance) + c = ICRS(ra=raarr * u.deg, dec=decarr * u.deg, distance=distance) g = c.transform_to(Galactic()) assert g.l.shape == arrshape @@ -186,10 +202,10 @@ def test_array_precession(): """ Ensures that FK5 coordinates as arrays precess their equinoxes """ - j2000 = Time('J2000') - j1975 = Time('J1975') + j2000 = Time("J2000") + j1975 = Time("J1975") - fk5 = FK5([1, 1.1]*u.radian, [0.5, 0.6]*u.radian) + fk5 = FK5([1, 1.1] * u.radian, [0.5, 0.6] * u.radian) assert fk5.equinox.jyear == j2000.jyear fk5_2 = fk5.transform_to(FK5(equinox=j1975)) assert fk5_2.equinox.jyear == j1975.jyear @@ -199,13 +215,13 @@ def test_array_precession(): def test_array_separation(): - c1 = ICRS([0, 0]*u.deg, [0, 0]*u.deg) - c2 = ICRS([1, 2]*u.deg, [0, 0]*u.deg) + c1 = ICRS([0, 0] * u.deg, [0, 0] * u.deg) + c2 = ICRS([1, 2] * u.deg, [0, 0] * u.deg) npt.assert_array_almost_equal(c1.separation(c2).degree, [1, 2]) - c3 = ICRS([0, 3.]*u.deg, [0., 0]*u.deg, distance=[1, 1.] * u.kpc) - c4 = ICRS([1, 1.]*u.deg, [0., 0]*u.deg, distance=[1, 1.] * u.kpc) + c3 = ICRS([0, 3.0] * u.deg, [0.0, 0] * u.deg, distance=[1, 1.0] * u.kpc) + c4 = ICRS([1, 1.0] * u.deg, [0.0, 0] * u.deg, distance=[1, 1.0] * u.kpc) # the 3-1 separation should be twice the 0-1 separation, but not *exactly* the same sep = c3.separation_3d(c4) @@ -218,9 +234,9 @@ def test_array_separation(): def test_array_indexing(): ra = np.linspace(0, 360, 10) dec = np.linspace(-90, 90, 10) - j1975 = Time(1975, format='jyear') + j1975 = Time(1975, format="jyear") - c1 = FK5(ra*u.deg, dec*u.deg, equinox=j1975) + c1 = FK5(ra * u.deg, dec * u.deg, equinox=j1975) c2 = c1[4] assert c2.ra.degree == 160 @@ -247,24 +263,24 @@ def test_array_len(): ra = np.linspace(0, 360, length) dec = np.linspace(0, 90, length) - c = ICRS(ra*u.deg, dec*u.deg) + c = ICRS(ra * u.deg, dec * u.deg) assert len(c) == length assert c.shape == (length,) with pytest.raises(TypeError): - c = ICRS(0*u.deg, 0*u.deg) + c = ICRS(0 * u.deg, 0 * u.deg) len(c) assert c.shape == tuple() def test_array_eq(): - c1 = ICRS([1, 2]*u.deg, [3, 4]*u.deg) - c2 = ICRS([1, 2]*u.deg, [3, 5]*u.deg) - c3 = ICRS([1, 3]*u.deg, [3, 4]*u.deg) - c4 = ICRS([1, 2]*u.deg, [3, 4.2]*u.deg) + c1 = ICRS([1, 2] * u.deg, [3, 4] * u.deg) + c2 = ICRS([1, 2] * u.deg, [3, 5] * u.deg) + c3 = ICRS([1, 3] * u.deg, [3, 4] * u.deg) + c4 = ICRS([1, 2] * u.deg, [3, 4.2] * u.deg) assert np.all(c1 == c1) assert np.any(c1 != c2) diff --git a/astropy/coordinates/tests/test_atc_replacements.py b/astropy/coordinates/tests/test_atc_replacements.py index d10e08a204b..627ca85ada2 100644 --- a/astropy/coordinates/tests/test_atc_replacements.py +++ b/astropy/coordinates/tests/test_atc_replacements.py @@ -12,17 +12,18 @@ from astropy.time import Time # Hard-coded random values -sph = SphericalRepresentation(lon=[15., 214.] * u.deg, - lat=[-12., 64.] * u.deg, - distance=[1, 1.]) +sph = SphericalRepresentation( + lon=[15.0, 214.0] * u.deg, lat=[-12.0, 64.0] * u.deg, distance=[1, 1.0] +) -@pytest.mark.parametrize('t', [Time("2014-06-25T00:00"), - Time(["2014-06-25T00:00", "2014-09-24"])]) -@pytest.mark.parametrize('pos', [sph[0], sph]) +@pytest.mark.parametrize( + "t", [Time("2014-06-25T00:00"), Time(["2014-06-25T00:00", "2014-09-24"])] +) +@pytest.mark.parametrize("pos", [sph[0], sph]) def test_atciqz_aticq(t, pos): """Check replacements against erfa versions for consistency.""" - jd1, jd2 = get_jd12(t, 'tdb') + jd1, jd2 = get_jd12(t, "tdb") astrom, _ = erfa.apci13(jd1, jd2) ra = pos.lon.to_value(u.rad) diff --git a/astropy/coordinates/tests/test_celestial_transformations.py b/astropy/coordinates/tests/test_celestial_transformations.py index d322eb8f451..a0b3eeb30b7 100644 --- a/astropy/coordinates/tests/test_celestial_transformations.py +++ b/astropy/coordinates/tests/test_celestial_transformations.py @@ -32,8 +32,12 @@ # used below in the next parametrized test m31_sys = [ICRS, FK5, FK4, Galactic] -m31_coo = [(10.6847929, 41.2690650), (10.6847929, 41.2690650), - (10.0004738, 40.9952444), (121.1744050, -21.5729360)] +m31_coo = [ + (10.6847929, 41.2690650), + (10.6847929, 41.2690650), + (10.0004738, 40.9952444), + (121.1744050, -21.5729360), +] m31_dist = Distance(770, u.kpc) convert_precision = 1 * u.arcsec roundtrip_precision = 1e-4 * u.degree @@ -46,21 +50,22 @@ m31_params.append((m31_sys[i], m31_sys[j], m31_coo[i], m31_coo[j])) -@pytest.mark.parametrize(('fromsys', 'tosys', 'fromcoo', 'tocoo'), m31_params) +@pytest.mark.parametrize(("fromsys", "tosys", "fromcoo", "tocoo"), m31_params) def test_m31_coord_transforms(fromsys, tosys, fromcoo, tocoo): """ This tests a variety of coordinate conversions for the Chandra point-source catalog location of M31 from NED. """ - coo1 = fromsys(ra=fromcoo[0]*u.deg, dec=fromcoo[1]*u.deg, distance=m31_dist) + coo1 = fromsys(ra=fromcoo[0] * u.deg, dec=fromcoo[1] * u.deg, distance=m31_dist) coo2 = coo1.transform_to(tosys()) if tosys is FK4: - coo2_prec = coo2.transform_to(FK4(equinox=Time('B1950'))) - assert (coo2_prec.spherical.lon - tocoo[0]*u.deg) < convert_precision # <1 arcsec - assert (coo2_prec.spherical.lat - tocoo[1]*u.deg) < convert_precision + coo2_prec = coo2.transform_to(FK4(equinox=Time("B1950"))) + # convert_precision <1 arcsec + assert (coo2_prec.spherical.lon - tocoo[0] * u.deg) < convert_precision + assert (coo2_prec.spherical.lat - tocoo[1] * u.deg) < convert_precision else: - assert (coo2.spherical.lon - tocoo[0]*u.deg) < convert_precision # <1 arcsec - assert (coo2.spherical.lat - tocoo[1]*u.deg) < convert_precision + assert (coo2.spherical.lon - tocoo[0] * u.deg) < convert_precision # <1 arcsec + assert (coo2.spherical.lat - tocoo[1] * u.deg) < convert_precision assert coo1.distance.unit == u.kpc assert coo2.distance.unit == u.kpc assert m31_dist.unit == u.kpc @@ -68,8 +73,8 @@ def test_m31_coord_transforms(fromsys, tosys, fromcoo, tocoo): # check round-tripping coo1_2 = coo2.transform_to(fromsys()) - assert (coo1_2.spherical.lon - fromcoo[0]*u.deg) < roundtrip_precision - assert (coo1_2.spherical.lat - fromcoo[1]*u.deg) < roundtrip_precision + assert (coo1_2.spherical.lon - fromcoo[0] * u.deg) < roundtrip_precision + assert (coo1_2.spherical.lat - fromcoo[1] * u.deg) < roundtrip_precision assert (coo1_2.distance - m31_dist) < dist_precision @@ -77,17 +82,17 @@ def test_precession(): """ Ensures that FK4 and FK5 coordinates precess their equinoxes """ - j2000 = Time('J2000') - b1950 = Time('B1950') - j1975 = Time('J1975') - b1975 = Time('B1975') + j2000 = Time("J2000") + b1950 = Time("B1950") + j1975 = Time("J1975") + b1975 = Time("B1975") - fk4 = FK4(ra=1*u.radian, dec=0.5*u.radian) + fk4 = FK4(ra=1 * u.radian, dec=0.5 * u.radian) assert fk4.equinox.byear == b1950.byear fk4_2 = fk4.transform_to(FK4(equinox=b1975)) assert fk4_2.equinox.byear == b1975.byear - fk5 = FK5(ra=1*u.radian, dec=0.5*u.radian) + fk5 = FK5(ra=1 * u.radian, dec=0.5 * u.radian) assert fk5.equinox.jyear == j2000.jyear fk5_2 = fk5.transform_to(FK4(equinox=j1975)) assert fk5_2.equinox.jyear == j1975.jyear @@ -98,57 +103,65 @@ def test_fk5_galactic(): Check that FK5 -> Galactic gives the same as FK5 -> FK4 -> Galactic. """ - fk5 = FK5(ra=1*u.deg, dec=2*u.deg) + fk5 = FK5(ra=1 * u.deg, dec=2 * u.deg) direct = fk5.transform_to(Galactic()) indirect = fk5.transform_to(FK4()).transform_to(Galactic()) - assert direct.separation(indirect).degree < 1.e-10 + assert direct.separation(indirect).degree < 1.0e-10 direct = fk5.transform_to(Galactic()) indirect = fk5.transform_to(FK4NoETerms()).transform_to(Galactic()) - assert direct.separation(indirect).degree < 1.e-10 + assert direct.separation(indirect).degree < 1.0e-10 def test_galactocentric(): # when z_sun=0, transformation should be very similar to Galactic - icrs_coord = ICRS(ra=np.linspace(0, 360, 10)*u.deg, - dec=np.linspace(-90, 90, 10)*u.deg, - distance=1.*u.kpc) + icrs_coord = ICRS( + ra=np.linspace(0, 360, 10) * u.deg, + dec=np.linspace(-90, 90, 10) * u.deg, + distance=1.0 * u.kpc, + ) g_xyz = icrs_coord.transform_to(Galactic()).cartesian.xyz - with galactocentric_frame_defaults.set('pre-v4.0'): - gc_xyz = icrs_coord.transform_to(Galactocentric(z_sun=0*u.kpc)).cartesian.xyz + with galactocentric_frame_defaults.set("pre-v4.0"): + gc_xyz = icrs_coord.transform_to(Galactocentric(z_sun=0 * u.kpc)).cartesian.xyz diff = np.abs(g_xyz - gc_xyz) - assert allclose(diff[0], 8.3*u.kpc, atol=1E-5*u.kpc) - assert allclose(diff[1:], 0*u.kpc, atol=1E-5*u.kpc) + assert allclose(diff[0], 8.3 * u.kpc, atol=1e-5 * u.kpc) + assert allclose(diff[1:], 0 * u.kpc, atol=1e-5 * u.kpc) # generate some test coordinates - g = Galactic(l=[0, 0, 45, 315]*u.deg, b=[-45, 45, 0, 0]*u.deg, - distance=[np.sqrt(2)]*4*u.kpc) - with galactocentric_frame_defaults.set('pre-v4.0'): - xyz = g.transform_to(Galactocentric(galcen_distance=1.*u.kpc, z_sun=0.*u.pc)).cartesian.xyz - true_xyz = np.array([[0, 0, -1.], [0, 0, 1], [0, 1, 0], [0, -1, 0]]).T*u.kpc - assert allclose(xyz.to(u.kpc), true_xyz.to(u.kpc), atol=1E-5*u.kpc) + g = Galactic( + l=[0, 0, 45, 315] * u.deg, + b=[-45, 45, 0, 0] * u.deg, + distance=[np.sqrt(2)] * 4 * u.kpc, + ) + with galactocentric_frame_defaults.set("pre-v4.0"): + xyz = g.transform_to( + Galactocentric(galcen_distance=1.0 * u.kpc, z_sun=0.0 * u.pc) + ).cartesian.xyz + true_xyz = np.array([[0, 0, -1.0], [0, 0, 1], [0, 1, 0], [0, -1, 0]]).T * u.kpc + assert allclose(xyz.to(u.kpc), true_xyz.to(u.kpc), atol=1e-5 * u.kpc) # check that ND arrays work # from Galactocentric to Galactic - x = np.linspace(-10., 10., 100) * u.kpc - y = np.linspace(-10., 10., 100) * u.kpc + x = np.linspace(-10.0, 10.0, 100) * u.kpc + y = np.linspace(-10.0, 10.0, 100) * u.kpc z = np.zeros_like(x) # from Galactic to Galactocentric - l = np.linspace(15, 30., 100) * u.deg - b = np.linspace(-10., 10., 100) * u.deg + l = np.linspace(15, 30.0, 100) * u.deg + b = np.linspace(-10.0, 10.0, 100) * u.deg d = np.ones_like(l.value) * u.kpc - with galactocentric_frame_defaults.set('latest'): + with galactocentric_frame_defaults.set("latest"): g1 = Galactocentric(x=x, y=y, z=z) - g2 = Galactocentric(x=x.reshape(100, 1, 1), y=y.reshape(100, 1, 1), - z=z.reshape(100, 1, 1)) + g2 = Galactocentric( + x=x.reshape(100, 1, 1), y=y.reshape(100, 1, 1), z=z.reshape(100, 1, 1) + ) g1t = g1.transform_to(Galactic()) g2t = g2.transform_to(Galactic()) @@ -156,14 +169,18 @@ def test_galactocentric(): assert_allclose(g1t.cartesian.xyz, g2t.cartesian.xyz[:, :, 0, 0]) g1 = Galactic(l=l, b=b, distance=d) - g2 = Galactic(l=l.reshape(100, 1, 1), b=b.reshape(100, 1, 1), - distance=d.reshape(100, 1, 1)) + g2 = Galactic( + l=l.reshape(100, 1, 1), + b=b.reshape(100, 1, 1), + distance=d.reshape(100, 1, 1), + ) g1t = g1.transform_to(Galactocentric()) g2t = g2.transform_to(Galactocentric()) - np.testing.assert_almost_equal(g1t.cartesian.xyz.value, - g2t.cartesian.xyz.value[:, :, 0, 0]) + np.testing.assert_almost_equal( + g1t.cartesian.xyz.value, g2t.cartesian.xyz.value[:, :, 0, 0] + ) def test_supergalactic(): @@ -171,11 +188,11 @@ def test_supergalactic(): Check Galactic<->Supergalactic and Galactic<->ICRS conversion. """ # Check supergalactic North pole. - npole = Galactic(l=47.37*u.degree, b=+6.32*u.degree) + npole = Galactic(l=47.37 * u.degree, b=+6.32 * u.degree) assert allclose(npole.transform_to(Supergalactic()).sgb.deg, +90, atol=1e-9) # Check the origin of supergalactic longitude. - lon0 = Supergalactic(sgl=0*u.degree, sgb=0*u.degree) + lon0 = Supergalactic(sgl=0 * u.degree, sgb=0 * u.degree) lon0_gal = lon0.transform_to(Galactic()) assert allclose(lon0_gal.l.deg, 137.37, atol=1e-9) assert allclose(lon0_gal.b.deg, 0, atol=1e-9) @@ -184,17 +201,17 @@ def test_supergalactic(): # (https://ui.adsabs.harvard.edu/abs/2008A%26A...484..143F) # GRB 021219 - supergalactic = Supergalactic(sgl=29.91*u.degree, sgb=+73.72*u.degree) - icrs = SkyCoord('18h50m27s +31d57m17s') + supergalactic = Supergalactic(sgl=29.91 * u.degree, sgb=+73.72 * u.degree) + icrs = SkyCoord("18h50m27s +31d57m17s") assert supergalactic.separation(icrs) < 0.005 * u.degree # GRB 030320 - supergalactic = Supergalactic(sgl=-174.44*u.degree, sgb=+46.17*u.degree) - icrs = SkyCoord('17h51m36s -25d18m52s') + supergalactic = Supergalactic(sgl=-174.44 * u.degree, sgb=+46.17 * u.degree) + icrs = SkyCoord("17h51m36s -25d18m52s") assert supergalactic.separation(icrs) < 0.005 * u.degree -class TestHCRS(): +class TestHCRS: """ Check HCRS<->ICRS coordinate conversions. @@ -208,44 +225,49 @@ def setup_method(self): self.t2 = Time("2013-08-02T23:00") self.tarr = Time(["2013-02-02T23:00", "2013-08-02T23:00"]) - self.sun_icrs_scalar = ICRS(ra=244.52984668*u.deg, - dec=-22.36943723*u.deg, - distance=406615.66347377*u.km) + self.sun_icrs_scalar = ICRS( + ra=244.52984668 * u.deg, + dec=-22.36943723 * u.deg, + distance=406615.66347377 * u.km, + ) # array of positions corresponds to times in `tarr` - self.sun_icrs_arr = ICRS(ra=[244.52989062, 271.40976248]*u.deg, - dec=[-22.36943605, -25.07431079]*u.deg, - distance=[406615.66347377, 375484.13558956]*u.km) + self.sun_icrs_arr = ICRS( + ra=[244.52989062, 271.40976248] * u.deg, + dec=[-22.36943605, -25.07431079] * u.deg, + distance=[406615.66347377, 375484.13558956] * u.km, + ) # corresponding HCRS positions - self.sun_hcrs_t1 = HCRS(CartesianRepresentation([0.0, 0.0, 0.0] * u.km), - obstime=self.t1) + self.sun_hcrs_t1 = HCRS( + CartesianRepresentation([0.0, 0.0, 0.0] * u.km), obstime=self.t1 + ) twod_rep = CartesianRepresentation([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] * u.km) self.sun_hcrs_tarr = HCRS(twod_rep, obstime=self.tarr) - self.tolerance = 5*u.km + self.tolerance = 5 * u.km def test_from_hcrs(self): # test scalar transform transformed = self.sun_hcrs_t1.transform_to(ICRS()) separation = transformed.separation_3d(self.sun_icrs_scalar) - assert_allclose(separation, 0*u.km, atol=self.tolerance) + assert_allclose(separation, 0 * u.km, atol=self.tolerance) # test non-scalar positions and times transformed = self.sun_hcrs_tarr.transform_to(ICRS()) separation = transformed.separation_3d(self.sun_icrs_arr) - assert_allclose(separation, 0*u.km, atol=self.tolerance) + assert_allclose(separation, 0 * u.km, atol=self.tolerance) def test_from_icrs(self): # scalar positions transformed = self.sun_icrs_scalar.transform_to(HCRS(obstime=self.t1)) separation = transformed.separation_3d(self.sun_hcrs_t1) - assert_allclose(separation, 0*u.km, atol=self.tolerance) + assert_allclose(separation, 0 * u.km, atol=self.tolerance) # nonscalar positions transformed = self.sun_icrs_arr.transform_to(HCRS(obstime=self.tarr)) separation = transformed.separation_3d(self.sun_hcrs_tarr) - assert_allclose(separation, 0*u.km, atol=self.tolerance) + assert_allclose(separation, 0 * u.km, atol=self.tolerance) -class TestHelioBaryCentric(): +class TestHelioBaryCentric: """ Check GCRS<->Heliocentric and Barycentric coordinate conversions. @@ -253,7 +275,7 @@ class TestHelioBaryCentric(): """ def setup_method(self): - wht = EarthLocation(342.12*u.deg, 28.758333333333333*u.deg, 2327*u.m) + wht = EarthLocation(342.12 * u.deg, 28.758333333333333 * u.deg, 2327 * u.m) self.obstime = Time("2013-02-02T23:00") self.wht_itrs = wht.get_itrs(obstime=self.obstime) @@ -261,61 +283,73 @@ def test_heliocentric(self): gcrs = self.wht_itrs.transform_to(GCRS(obstime=self.obstime)) helio = gcrs.transform_to(HCRS(obstime=self.obstime)) # Check it doesn't change from previous times. - previous = [-1.02597256e+11, 9.71725820e+10, 4.21268419e+10] * u.m + previous = [-1.02597256e11, 9.71725820e10, 4.21268419e10] * u.m assert_allclose(helio.cartesian.xyz, previous) # And that it agrees with SLALIB to within 14km helio_slalib = [-0.685820296, 0.6495585893, 0.2816005464] * u.au - assert np.sqrt(((helio.cartesian.xyz - - helio_slalib)**2).sum()) < 14. * u.km + assert np.sqrt(((helio.cartesian.xyz - helio_slalib) ** 2).sum()) < 14.0 * u.km def test_barycentric(self): gcrs = self.wht_itrs.transform_to(GCRS(obstime=self.obstime)) bary = gcrs.transform_to(ICRS()) - previous = [-1.02758958e+11, 9.68331109e+10, 4.19720938e+10] * u.m + previous = [-1.02758958e11, 9.68331109e10, 4.19720938e10] * u.m assert_allclose(bary.cartesian.xyz, previous) # And that it agrees with SLALIB answer to within 14km bary_slalib = [-0.6869012079, 0.6472893646, 0.2805661191] * u.au - assert np.sqrt(((bary.cartesian.xyz - - bary_slalib)**2).sum()) < 14. * u.km + assert np.sqrt(((bary.cartesian.xyz - bary_slalib) ** 2).sum()) < 14.0 * u.km def test_lsr_sanity(): - # random numbers, but zero velocity in ICRS frame - icrs = ICRS(ra=15.1241*u.deg, dec=17.5143*u.deg, distance=150.12*u.pc, - pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, - radial_velocity=0*u.km/u.s) + icrs = ICRS( + ra=15.1241 * u.deg, + dec=17.5143 * u.deg, + distance=150.12 * u.pc, + pm_ra_cosdec=0 * u.mas / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=0 * u.km / u.s, + ) lsr = icrs.transform_to(LSR()) - lsr_diff = lsr.data.differentials['s'] + lsr_diff = lsr.data.differentials["s"] cart_lsr_vel = lsr_diff.represent_as(CartesianRepresentation, base=lsr.data) lsr_vel = ICRS(cart_lsr_vel) gal_lsr = lsr_vel.transform_to(Galactic()).cartesian.xyz - assert allclose(gal_lsr.to(u.km/u.s, u.dimensionless_angles()), - lsr.v_bary.d_xyz) + assert allclose(gal_lsr.to(u.km / u.s, u.dimensionless_angles()), lsr.v_bary.d_xyz) # moving with LSR velocity - lsr = LSR(ra=15.1241*u.deg, dec=17.5143*u.deg, distance=150.12*u.pc, - pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, - radial_velocity=0*u.km/u.s) + lsr = LSR( + ra=15.1241 * u.deg, + dec=17.5143 * u.deg, + distance=150.12 * u.pc, + pm_ra_cosdec=0 * u.mas / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=0 * u.km / u.s, + ) icrs = lsr.transform_to(ICRS()) - icrs_diff = icrs.data.differentials['s'] + icrs_diff = icrs.data.differentials["s"] cart_vel = icrs_diff.represent_as(CartesianRepresentation, base=icrs.data) vel = ICRS(cart_vel) gal_icrs = vel.transform_to(Galactic()).cartesian.xyz - assert allclose(gal_icrs.to(u.km/u.s, u.dimensionless_angles()), - -lsr.v_bary.d_xyz) + assert allclose( + gal_icrs.to(u.km / u.s, u.dimensionless_angles()), -lsr.v_bary.d_xyz + ) def test_hcrs_icrs_differentials(): # Regression to ensure that we can transform velocities from HCRS to LSR. # Numbers taken from the original issue, gh-6835. - hcrs = HCRS(ra=8.67*u.deg, dec=53.09*u.deg, distance=117*u.pc, - pm_ra_cosdec=4.8*u.mas/u.yr, pm_dec=-15.16*u.mas/u.yr, - radial_velocity=23.42*u.km/u.s) + hcrs = HCRS( + ra=8.67 * u.deg, + dec=53.09 * u.deg, + distance=117 * u.pc, + pm_ra_cosdec=4.8 * u.mas / u.yr, + pm_dec=-15.16 * u.mas / u.yr, + radial_velocity=23.42 * u.km / u.s, + ) icrs = hcrs.transform_to(ICRS()) # The position and velocity should not change much @@ -334,10 +368,12 @@ def test_cirs_icrs(): Test CIRS<->ICRS transformations, including self transform """ t = Time("J2010") - MOONDIST = 385000*u.km # approximate moon semi-major orbit axis of moon - MOONDIST_CART = CartesianRepresentation(3**-0.5*MOONDIST, 3**-0.5*MOONDIST, 3**-0.5*MOONDIST) + MOONDIST = 385000 * u.km # approximate moon semi-major orbit axis of moon + MOONDIST_CART = CartesianRepresentation( + 3**-0.5 * MOONDIST, 3**-0.5 * MOONDIST, 3**-0.5 * MOONDIST + ) - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg) cirs_geo_frame = CIRS(obstime=t) cirs_topo_frame = CIRS(obstime=t, location=loc) @@ -345,7 +381,11 @@ def test_cirs_icrs(): moon_topo = moon_geo.transform_to(cirs_topo_frame) # now check that the distance change is similar to earth radius - assert 1000*u.km < np.abs(moon_topo.distance - moon_geo.distance).to(u.au) < 7000*u.km + assert ( + 1000 * u.km + < np.abs(moon_topo.distance - moon_geo.distance).to(u.au) + < 7000 * u.km + ) # now check that it round-trips moon2 = moon_topo.transform_to(moon_geo) @@ -353,15 +393,15 @@ def test_cirs_icrs(): # now check ICRS transform gives a decent distance from Barycentre moon_icrs = moon_geo.transform_to(ICRS()) - assert_allclose(moon_icrs.distance - 1*u.au, 0.0*u.R_sun, atol=3*u.R_sun) + assert_allclose(moon_icrs.distance - 1 * u.au, 0.0 * u.R_sun, atol=3 * u.R_sun) -@pytest.mark.parametrize('frame', [LSR, GalacticLSR]) +@pytest.mark.parametrize("frame", [LSR, GalacticLSR]) def test_lsr_loopback(frame): - xyz = CartesianRepresentation(1, 2, 3)*u.AU - xyz = xyz.with_differentials(CartesianDifferential(4, 5, 6)*u.km/u.s) + xyz = CartesianRepresentation(1, 2, 3) * u.AU + xyz = xyz.with_differentials(CartesianDifferential(4, 5, 6) * u.km / u.s) - v_bary = CartesianDifferential(5, 10, 15)*u.km/u.s + v_bary = CartesianDifferential(5, 10, 15) * u.km / u.s # Test that the loopback properly handles a change in v_bary from_coo = frame(xyz) # default v_bary @@ -372,20 +412,28 @@ def test_lsr_loopback(frame): # Confirm that the explicit transformation changes the velocity but not the position assert allclose(explicit_coo.cartesian.xyz, from_coo.cartesian.xyz, rtol=1e-10) - assert not allclose(explicit_coo.velocity.d_xyz, from_coo.velocity.d_xyz, rtol=1e-10) + assert not allclose( + explicit_coo.velocity.d_xyz, from_coo.velocity.d_xyz, rtol=1e-10 + ) # Confirm that the loopback matches the explicit transformation assert allclose(explicit_coo.cartesian.xyz, implicit_coo.cartesian.xyz, rtol=1e-10) - assert allclose(explicit_coo.velocity.d_xyz, implicit_coo.velocity.d_xyz, rtol=1e-10) - - -@pytest.mark.parametrize('to_frame', - [Galactocentric(galcen_coord=ICRS(300*u.deg, -30*u.deg)), - Galactocentric(galcen_distance=10*u.kpc), - Galactocentric(z_sun=10*u.pc), - Galactocentric(roll=1*u.deg)]) + assert allclose( + explicit_coo.velocity.d_xyz, implicit_coo.velocity.d_xyz, rtol=1e-10 + ) + + +@pytest.mark.parametrize( + "to_frame", + [ + Galactocentric(galcen_coord=ICRS(300 * u.deg, -30 * u.deg)), + Galactocentric(galcen_distance=10 * u.kpc), + Galactocentric(z_sun=10 * u.pc), + Galactocentric(roll=1 * u.deg), + ], +) def test_galactocentric_loopback(to_frame): - xyz = CartesianRepresentation(1, 2, 3)*u.pc + xyz = CartesianRepresentation(1, 2, 3) * u.pc from_coo = Galactocentric(xyz) diff --git a/astropy/coordinates/tests/test_distance.py b/astropy/coordinates/tests/test_distance.py index 33a478c5b62..b112f4f05ac 100644 --- a/astropy/coordinates/tests/test_distance.py +++ b/astropy/coordinates/tests/test_distance.py @@ -23,10 +23,10 @@ def test_distances(): transformations. """ - ''' + """ Distances can also be specified, and allow for a full 3D definition of a coordinate. - ''' + """ # try all the different ways to initialize a Distance distance = Distance(12, u.parsec) @@ -37,7 +37,7 @@ def test_distances(): with pytest.raises(u.UnitsError): Distance(12) - with pytest.raises(ValueError, match='none of `value`, `z`, `distmod`,'): + with pytest.raises(ValueError, match="none of `value`, `z`, `distmod`,"): Distance(unit=u.km) # standard units are pre-defined @@ -46,8 +46,11 @@ def test_distances(): # Coordinate objects can be assigned a distance object, giving them a full # 3D position - c = Galactic(l=158.558650*u.degree, b=-43.350066*u.degree, - distance=Distance(12, u.parsec)) + c = Galactic( + l=158.558650 * u.degree, + b=-43.350066 * u.degree, + distance=Distance(12, u.parsec), + ) assert quantity_allclose(c.distance, 12 * u.pc) # or initialize distances via redshifts - this is actually tested in the @@ -58,7 +61,7 @@ def test_distances(): # Coordinate objects can be initialized with a distance using special # syntax - c1 = Galactic(l=158.558650*u.deg, b=-43.350066*u.deg, distance=12 * u.kpc) + c1 = Galactic(l=158.558650 * u.deg, b=-43.350066 * u.deg, distance=12 * u.kpc) # Coordinate objects can be instantiated with cartesian coordinates # Internally they will immediately be converted to two angles + a distance @@ -71,10 +74,10 @@ def test_distances(): assert isinstance(sep12, Distance) npt.assert_allclose(sep12.pc, 12005.784163916317, 10) - ''' + """ All spherical coordinate systems with distances can be converted to cartesian coordinates. - ''' + """ cartrep2 = c2.cartesian assert isinstance(cartrep2.x, u.Quantity) @@ -83,10 +86,11 @@ def test_distances(): npt.assert_allclose(cartrep2.z.value, 8) # with no distance, the unit sphere is assumed when converting to cartesian - c3 = Galactic(l=158.558650*u.degree, b=-43.350066*u.degree, distance=None) + c3 = Galactic(l=158.558650 * u.degree, b=-43.350066 * u.degree, distance=None) unitcart = c3.cartesian - npt.assert_allclose(((unitcart.x**2 + unitcart.y**2 + - unitcart.z**2)**0.5).value, 1.0) + npt.assert_allclose( + ((unitcart.x**2 + unitcart.y**2 + unitcart.z**2) ** 0.5).value, 1.0 + ) # TODO: choose between these when CartesianRepresentation gets a definite # decision on whether or not it gets __add__ @@ -106,7 +110,7 @@ def test_distances(): npt.assert_allclose(csum.distance.kpc, 11.9942200501) -@pytest.mark.skipif(not HAS_SCIPY, reason='Requires scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") def test_distances_scipy(): """ The distance-related tests that require scipy due to the cosmology @@ -122,14 +126,14 @@ def test_distances_scipy(): npt.assert_allclose(d5.compute_z(WMAP5), 0.23, rtol=1e-8) d6 = Distance(z=0.23, cosmology=WMAP5, unit=u.km) - npt.assert_allclose(d6.value, 3.5417046898762366e+22) + npt.assert_allclose(d6.value, 3.5417046898762366e22) - with pytest.raises(ValueError, match='a `cosmology` was given but `z`'): - Distance(parallax=1*u.mas, cosmology=WMAP5) + with pytest.raises(ValueError, match="a `cosmology` was given but `z`"): + Distance(parallax=1 * u.mas, cosmology=WMAP5) # Regression test for #12531 with pytest.raises(ValueError, match=MULTIPLE_INPUTS_ERROR_MSG): - Distance(z=0.23, parallax=1*u.mas) + Distance(z=0.23, parallax=1 * u.mas) # vectors! regression test for #11949 d4 = Distance(z=[0.23, 0.45]) # as of writing, Planck18 @@ -137,7 +141,6 @@ def test_distances_scipy(): def test_distance_change(): - ra = Longitude("4:08:15.162342", unit=u.hour) dec = Latitude("-41:08:15.162342", unit=u.degree) c1 = ICRS(ra, dec, Distance(1, unit=u.kpc)) @@ -179,13 +182,12 @@ def test_distance_is_quantity(): assert d.value[1] != 0 # regression test against #2261 - d = Distance([2 * u.kpc, 250. * u.pc]) + d = Distance([2 * u.kpc, 250.0 * u.pc]) assert d.unit is u.kpc - assert np.all(d.value == np.array([2., 0.25])) + assert np.all(d.value == np.array([2.0, 0.25])) def test_distmod(): - d = Distance(10, u.pc) assert d.distmod.value == 0 @@ -193,14 +195,14 @@ def test_distmod(): assert d.distmod.value == 20 assert d.kpc == 100 - d = Distance(distmod=-1., unit=u.au) + d = Distance(distmod=-1.0, unit=u.au) npt.assert_allclose(d.value, 1301442.9440836983) with pytest.raises(ValueError, match=MULTIPLE_INPUTS_ERROR_MSG): d = Distance(value=d, distmod=20) with pytest.raises(ValueError, match=MULTIPLE_INPUTS_ERROR_MSG): - d = Distance(z=.23, distmod=20) + d = Distance(z=0.23, distmod=20) # check the Mpc/kpc/pc behavior assert Distance(distmod=1).unit == u.pc @@ -213,20 +215,19 @@ def test_distmod(): def test_parallax(): - - d = Distance(parallax=1*u.arcsecond) - assert d.pc == 1. + d = Distance(parallax=1 * u.arcsecond) + assert d.pc == 1.0 with pytest.raises(ValueError, match=MULTIPLE_INPUTS_ERROR_MSG): - d = Distance(15*u.pc, parallax=20*u.milliarcsecond) + d = Distance(15 * u.pc, parallax=20 * u.milliarcsecond) with pytest.raises(ValueError, match=MULTIPLE_INPUTS_ERROR_MSG): - d = Distance(parallax=20*u.milliarcsecond, distmod=20) + d = Distance(parallax=20 * u.milliarcsecond, distmod=20) # array - plx = [1, 10, 100.]*u.mas + plx = [1, 10, 100.0] * u.mas d = Distance(parallax=plx) - assert quantity_allclose(d.pc, [1000., 100., 10.]) + assert quantity_allclose(d.pc, [1000.0, 100.0, 10.0]) assert quantity_allclose(plx, d.parallax) error_message = ( @@ -247,8 +248,8 @@ def test_parallax(): Distance(parallax=[10, 1, -1] * u.mas, allow_negative=True) # Regression test for #12569; `unit` was ignored if `parallax` was given. - d = Distance(parallax=1*u.mas, unit=u.kpc) - assert d.value == 1. + d = Distance(parallax=1 * u.mas, unit=u.kpc) + assert d.value == 1.0 assert d.unit is u.kpc @@ -260,7 +261,7 @@ def test_distance_in_coordinates(): ra = Longitude("4:08:15.162342", unit=u.hour) dec = Latitude("-41:08:15.162342", unit=u.degree) - coo = ICRS(ra, dec, distance=2*u.kpc) + coo = ICRS(ra, dec, distance=2 * u.kpc) cart = coo.cartesian @@ -268,11 +269,12 @@ def test_distance_in_coordinates(): def test_negative_distance(): - """ Test optional kwarg allow_negative """ + """Test optional kwarg allow_negative""" error_message = ( r"^distance must be >= 0\. Use the argument `allow_negative=True` to allow " - r"negative values\.$") + r"negative values\.$" + ) with pytest.raises(ValueError, match=error_message): Distance([-2, 3.1], u.kpc) @@ -289,20 +291,20 @@ def test_negative_distance(): def test_distance_comparison(): """Ensure comparisons of distances work (#2206, #2250)""" - a = Distance(15*u.kpc) - b = Distance(15*u.kpc) + a = Distance(15 * u.kpc) + b = Distance(15 * u.kpc) assert a == b - c = Distance(1.*u.Mpc) + c = Distance(1.0 * u.Mpc) assert a < c def test_distance_to_quantity_when_not_units_of_length(): """Any operation that leaves units other than those of length should turn a distance into a quantity (#2206, #2250)""" - d = Distance(15*u.kpc) - twice = 2.*d + d = Distance(15 * u.kpc) + twice = 2.0 * d assert isinstance(twice, Distance) - area = 4.*np.pi*d**2 + area = 4.0 * np.pi * d**2 assert area.unit.is_equivalent(u.m**2) assert not isinstance(area, Distance) assert type(area) is u.Quantity diff --git a/astropy/coordinates/tests/test_earth.py b/astropy/coordinates/tests/test_earth.py index e6c36d837e9..578a4a25581 100644 --- a/astropy/coordinates/tests/test_earth.py +++ b/astropy/coordinates/tests/test_earth.py @@ -16,15 +16,15 @@ from astropy.units import allclose as quantity_allclose -def allclose_m14(a, b, rtol=1.e-14, atol=None): +def allclose_m14(a, b, rtol=1.0e-14, atol=None): if atol is None: - atol = 1.e-14 * getattr(a, 'unit', 1) + atol = 1.0e-14 * getattr(a, "unit", 1) return quantity_allclose(a, b, rtol, atol) -def allclose_m8(a, b, rtol=1.e-8, atol=None): +def allclose_m8(a, b, rtol=1.0e-8, atol=None): if atol is None: - atol = 1.e-8 * getattr(a, 'unit', 1) + atol = 1.0e-8 * getattr(a, "unit", 1) return quantity_allclose(a, b, rtol, atol) @@ -48,19 +48,19 @@ def test_gc2gd(): status = 0 # help for copy & paste of vvd location = EarthLocation.from_geocentric(x, y, z, u.m) - e, p, h = location.to_geodetic('WGS84') + e, p, h = location.to_geodetic("WGS84") e, p, h = e.to(u.radian), p.to(u.radian), h.to(u.m) vvd(e, 0.98279372324732907, 1e-14, "eraGc2gd", "e2", status) vvd(p, 0.97160184820607853, 1e-14, "eraGc2gd", "p2", status) vvd(h, 331.41731754844348, 1e-8, "eraGc2gd", "h2", status) - e, p, h = location.to_geodetic('GRS80') + e, p, h = location.to_geodetic("GRS80") e, p, h = e.to(u.radian), p.to(u.radian), h.to(u.m) vvd(e, 0.98279372324732907, 1e-14, "eraGc2gd", "e2", status) vvd(p, 0.97160184820607853, 1e-14, "eraGc2gd", "p2", status) vvd(h, 331.41731754844348, 1e-8, "eraGc2gd", "h2", status) - e, p, h = location.to_geodetic('WGS72') + e, p, h = location.to_geodetic("WGS72") e, p, h = e.to(u.radian), p.to(u.radian), h.to(u.m) vvd(e, 0.98279372324732907, 1e-14, "eraGc2gd", "e3", status) vvd(p, 0.97160181811015119, 1e-14, "eraGc2gd", "p3", status) @@ -75,31 +75,34 @@ def test_gd2gc(): status = 0 # help for copy & paste of vvd - location = EarthLocation.from_geodetic(e, p, h, ellipsoid='WGS84') + location = EarthLocation.from_geodetic(e, p, h, ellipsoid="WGS84") xyz = tuple(v.to(u.m) for v in location.to_geocentric()) vvd(xyz[0], -5599000.5577049947, 1e-7, "eraGd2gc", "0/1", status) vvd(xyz[1], 233011.67223479203, 1e-7, "eraGd2gc", "1/1", status) vvd(xyz[2], -3040909.4706983363, 1e-7, "eraGd2gc", "2/1", status) - location = EarthLocation.from_geodetic(e, p, h, ellipsoid='GRS80') + location = EarthLocation.from_geodetic(e, p, h, ellipsoid="GRS80") xyz = tuple(v.to(u.m) for v in location.to_geocentric()) vvd(xyz[0], -5599000.5577260984, 1e-7, "eraGd2gc", "0/2", status) vvd(xyz[1], 233011.6722356703, 1e-7, "eraGd2gc", "1/2", status) vvd(xyz[2], -3040909.4706095476, 1e-7, "eraGd2gc", "2/2", status) - location = EarthLocation.from_geodetic(e, p, h, ellipsoid='WGS72') + location = EarthLocation.from_geodetic(e, p, h, ellipsoid="WGS72") xyz = tuple(v.to(u.m) for v in location.to_geocentric()) vvd(xyz[0], -5598998.7626301490, 1e-7, "eraGd2gc", "0/3", status) vvd(xyz[1], 233011.5975297822, 1e-7, "eraGd2gc", "1/3", status) vvd(xyz[2], -3040908.6861467111, 1e-7, "eraGd2gc", "2/3", status) -class TestInput(): +class TestInput: def setup_method(self): - self.lon = Longitude([0., 45., 90., 135., 180., -180, -90, -45], u.deg, - wrap_angle=180*u.deg) - self.lat = Latitude([+0., 30., 60., +90., -90., -60., -30., 0.], u.deg) - self.h = u.Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11., -.1], u.m) + self.lon = Longitude( + [0.0, 45.0, 90.0, 135.0, 180.0, -180, -90, -45], + u.deg, + wrap_angle=180 * u.deg, + ) + self.lat = Latitude([+0.0, 30.0, 60.0, +90.0, -90.0, -60.0, -30.0, 0.0], u.deg) + self.h = u.Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11.0, -0.1], u.m) self.location = EarthLocation.from_geodetic(self.lon, self.lat, self.h) self.x, self.y, self.z = self.location.to_geocentric() @@ -107,12 +110,14 @@ def test_default_ellipsoid(self): assert self.location.ellipsoid == EarthLocation._ellipsoid def test_geo_attributes(self): - assert all(np.all(_1 == _2) - for _1, _2 in zip(self.location.geodetic, - self.location.to_geodetic())) - assert all(np.all(_1 == _2) - for _1, _2 in zip(self.location.geocentric, - self.location.to_geocentric())) + assert all( + np.all(_1 == _2) + for _1, _2 in zip(self.location.geodetic, self.location.to_geodetic()) + ) + assert all( + np.all(_1 == _2) + for _1, _2 in zip(self.location.geocentric, self.location.to_geocentric()) + ) def test_attribute_classes(self): """Test that attribute classes are correct (and not EarthLocation)""" @@ -129,31 +134,31 @@ def test_input(self): # units of length should be assumed geocentric geocentric = EarthLocation(self.x, self.y, self.z) assert np.all(geocentric == self.location) - geocentric2 = EarthLocation(self.x.value, self.y.value, self.z.value, - self.x.unit) + geocentric2 = EarthLocation( + self.x.value, self.y.value, self.z.value, self.x.unit + ) assert np.all(geocentric2 == self.location) geodetic = EarthLocation(self.lon, self.lat, self.h) assert np.all(geodetic == self.location) - geodetic2 = EarthLocation(self.lon.to_value(u.degree), - self.lat.to_value(u.degree), - self.h.to_value(u.m)) + geodetic2 = EarthLocation( + self.lon.to_value(u.degree), + self.lat.to_value(u.degree), + self.h.to_value(u.m), + ) assert np.all(geodetic2 == self.location) geodetic3 = EarthLocation(self.lon, self.lat) - assert allclose_m14(geodetic3.lon.value, - self.location.lon.value) - assert allclose_m14(geodetic3.lat.value, - self.location.lat.value) - assert not np.any(isclose_m14(geodetic3.height.value, - self.location.height.value)) + assert allclose_m14(geodetic3.lon.value, self.location.lon.value) + assert allclose_m14(geodetic3.lat.value, self.location.lat.value) + assert not np.any( + isclose_m14(geodetic3.height.value, self.location.height.value) + ) geodetic4 = EarthLocation(self.lon, self.lat, self.h[-1]) - assert allclose_m14(geodetic4.lon.value, - self.location.lon.value) - assert allclose_m14(geodetic4.lat.value, - self.location.lat.value) - assert allclose_m14(geodetic4.height[-1].value, - self.location.height[-1].value) - assert not np.any(isclose_m14(geodetic4.height[:-1].value, - self.location.height[:-1].value)) + assert allclose_m14(geodetic4.lon.value, self.location.lon.value) + assert allclose_m14(geodetic4.lat.value, self.location.lat.value) + assert allclose_m14(geodetic4.height[-1].value, self.location.height[-1].value) + assert not np.any( + isclose_m14(geodetic4.height[:-1].value, self.location.height[:-1].value) + ) # check length unit preservation geocentric5 = EarthLocation(self.x, self.y, self.z, u.pc) assert geocentric5.unit is u.pc @@ -180,8 +185,7 @@ def test_invalid_input(self): EarthLocation.from_geocentric(self.h, self.lon, self.lat) # floats without a unit with pytest.raises(TypeError): - EarthLocation.from_geocentric(self.x.value, self.y.value, - self.z.value) + EarthLocation.from_geocentric(self.x.value, self.y.value, self.z.value) # inconsistent shape with pytest.raises(ValueError): EarthLocation.from_geocentric(self.x, self.y, self.z[:5]) @@ -195,12 +199,13 @@ def test_invalid_input(self): def test_slicing(self): # test on WGS72 location, so we can check the ellipsoid is passed on - locwgs72 = EarthLocation.from_geodetic(self.lon, self.lat, self.h, - ellipsoid='WGS72') + locwgs72 = EarthLocation.from_geodetic( + self.lon, self.lat, self.h, ellipsoid="WGS72" + ) loc_slice1 = locwgs72[4] assert isinstance(loc_slice1, EarthLocation) assert loc_slice1.unit is locwgs72.unit - assert loc_slice1.ellipsoid == locwgs72.ellipsoid == 'WGS72' + assert loc_slice1.ellipsoid == locwgs72.ellipsoid == "WGS72" assert not loc_slice1.shape with pytest.raises(TypeError): loc_slice1[0] @@ -213,7 +218,7 @@ def test_slicing(self): assert loc_slice2.unit is locwgs72.unit assert loc_slice2.ellipsoid == locwgs72.ellipsoid assert loc_slice2.shape == (2,) - loc_x = locwgs72['x'] + loc_x = locwgs72["x"] assert type(loc_x) is u.Quantity assert loc_x.shape == locwgs72.shape assert loc_x.unit is locwgs72.unit @@ -221,18 +226,17 @@ def test_slicing(self): def test_invalid_ellipsoid(self): # unknown ellipsoid with pytest.raises(ValueError): - EarthLocation.from_geodetic(self.lon, self.lat, self.h, - ellipsoid='foo') + EarthLocation.from_geodetic(self.lon, self.lat, self.h, ellipsoid="foo") with pytest.raises(TypeError): - EarthLocation(self.lon, self.lat, self.h, ellipsoid='foo') + EarthLocation(self.lon, self.lat, self.h, ellipsoid="foo") with pytest.raises(ValueError): - self.location.ellipsoid = 'foo' + self.location.ellipsoid = "foo" with pytest.raises(ValueError): - self.location.to_geodetic('foo') + self.location.to_geodetic("foo") - @pytest.mark.parametrize('ellipsoid', ELLIPSOIDS) + @pytest.mark.parametrize("ellipsoid", ELLIPSOIDS) def test_ellipsoid(self, ellipsoid): """Test that different ellipsoids are understood, and differ""" # check that heights differ for different ellipsoids @@ -245,8 +249,9 @@ def test_ellipsoid(self, ellipsoid): assert not np.all(isclose_m8(h.value, self.h.value)) # given lon, lat, height, check that x,y,z differ - location = EarthLocation.from_geodetic(self.lon, self.lat, self.h, - ellipsoid=ellipsoid) + location = EarthLocation.from_geodetic( + self.lon, self.lat, self.h, ellipsoid=ellipsoid + ) if ellipsoid == self.location.ellipsoid: assert allclose_m14(location.z.value, self.z.value) else: @@ -258,8 +263,8 @@ def test_to_value(self): assert np.all(loc.value == loc_ndarray) loc2 = self.location.to(u.km) loc2_ndarray = np.empty_like(loc_ndarray) - for coo in 'x', 'y', 'z': - loc2_ndarray[coo] = loc_ndarray[coo] / 1000. + for coo in "x", "y", "z": + loc2_ndarray[coo] = loc_ndarray[coo] / 1000.0 assert np.all(loc2.value == loc2_ndarray) loc2_value = self.location.to_value(u.km) assert np.all(loc2_value == loc2_ndarray) @@ -267,7 +272,7 @@ def test_to_value(self): def test_pickling(): """Regression test against #4304.""" - el = EarthLocation(0.*u.m, 6000*u.km, 6000*u.km) + el = EarthLocation(0.0 * u.m, 6000 * u.km, 6000 * u.km) s = pickle.dumps(el) el2 = pickle.loads(s) assert el == el2 @@ -277,9 +282,9 @@ def test_repr_latex(): """ Regression test for issue #4542 """ - somelocation = EarthLocation(lon='149:3:57.9', lat='-31:16:37.3') + somelocation = EarthLocation(lon="149:3:57.9", lat="-31:16:37.3") somelocation._repr_latex_() - somelocation2 = EarthLocation(lon=[1., 2.]*u.deg, lat=[-1., 9.]*u.deg) + somelocation2 = EarthLocation(lon=[1.0, 2.0] * u.deg, lat=[-1.0, 9.0] * u.deg) somelocation2._repr_latex_() @@ -287,7 +292,7 @@ def test_repr_latex(): # TODO: this parametrize should include a second option with a valid Google API # key. For example, we should make an API key for Astropy, and add it to GitHub Actions # as an environment variable (for security). -@pytest.mark.parametrize('google_api_key', [None]) +@pytest.mark.parametrize("google_api_key", [None]) def test_of_address(google_api_key): NYC_lon = -74.0 * u.deg NYC_lat = 40.7 * u.deg @@ -302,12 +307,12 @@ def test_of_address(google_api_key): loc = EarthLocation.of_address("New York, NY") except NameResolveError as e: # API limit might surface even here in CI. - if 'unknown failure with' not in str(e): + if "unknown failure with" not in str(e): pytest.xfail(str(e)) else: assert quantity_allclose(loc.lat, NYC_lat, atol=NYC_tol) assert quantity_allclose(loc.lon, NYC_lon, atol=NYC_tol) - assert np.allclose(loc.height.value, 0.) + assert np.allclose(loc.height.value, 0.0) # Put this one here as buffer to get around Google map API limit per sec. # no match: This always raises NameResolveError @@ -326,13 +331,13 @@ def test_of_address(google_api_key): else: assert quantity_allclose(loc.lat, NYC_lat, atol=NYC_tol) assert quantity_allclose(loc.lon, NYC_lon, atol=NYC_tol) - assert quantity_allclose(loc.height, 10.438*u.meter, atol=1.*u.cm) + assert quantity_allclose(loc.height, 10.438 * u.meter, atol=1.0 * u.cm) def test_geodetic_tuple(): - lat = 2*u.deg - lon = 10*u.deg - height = 100*u.m + lat = 2 * u.deg + lon = 10 * u.deg + height = 100 * u.m el = EarthLocation.from_geodetic(lat=lat, lon=lon, height=height) @@ -345,61 +350,65 @@ def test_geodetic_tuple(): def test_gravitational_redshift(): - someloc = EarthLocation(lon=-87.7*u.deg, lat=37*u.deg) - sometime = Time('2017-8-21 18:26:40') + someloc = EarthLocation(lon=-87.7 * u.deg, lat=37 * u.deg) + sometime = Time("2017-8-21 18:26:40") zg0 = someloc.gravitational_redshift(sometime) # should be of order ~few mm/s change per week zg_week = someloc.gravitational_redshift(sometime + 7 * u.day) - assert 1.*u.mm/u.s < abs(zg_week - zg0) < 1*u.cm/u.s + assert 1.0 * u.mm / u.s < abs(zg_week - zg0) < 1 * u.cm / u.s # ~cm/s over a half-year zg_halfyear = someloc.gravitational_redshift(sometime + 0.5 * u.yr) - assert 1*u.cm/u.s < abs(zg_halfyear - zg0) < 1*u.dm/u.s + assert 1 * u.cm / u.s < abs(zg_halfyear - zg0) < 1 * u.dm / u.s # but when back to the same time in a year, should be tenths of mm # even over decades zg_year = someloc.gravitational_redshift(sometime - 20 * u.year) - assert .1*u.mm/u.s < abs(zg_year - zg0) < 1*u.mm/u.s + assert 0.1 * u.mm / u.s < abs(zg_year - zg0) < 1 * u.mm / u.s # Check mass adjustments. # If Jupiter and the moon are ignored, effect should be off by ~ .5 mm/s - masses = {'sun': constants.G*constants.M_sun, - 'jupiter': 0*constants.G*u.kg, - 'moon': 0*constants.G*u.kg} + masses = { + "sun": constants.G * constants.M_sun, + "jupiter": 0 * constants.G * u.kg, + "moon": 0 * constants.G * u.kg, + } zg_moonjup = someloc.gravitational_redshift(sometime, masses=masses) - assert .1*u.mm/u.s < abs(zg_moonjup - zg0) < 1*u.mm/u.s + assert 0.1 * u.mm / u.s < abs(zg_moonjup - zg0) < 1 * u.mm / u.s # Check that simply not including the bodies gives the same result. - assert zg_moonjup == someloc.gravitational_redshift(sometime, - bodies=('sun',)) + assert zg_moonjup == someloc.gravitational_redshift(sometime, bodies=("sun",)) # And that earth can be given, even not as last argument assert zg_moonjup == someloc.gravitational_redshift( - sometime, bodies=('earth', 'sun',)) + sometime, bodies=("earth", "sun") + ) # If the earth is also ignored, effect should be off by ~ 20 cm/s # This also tests the conversion of kg to gravitational units. - masses['earth'] = 0*u.kg + masses["earth"] = 0 * u.kg zg_moonjupearth = someloc.gravitational_redshift(sometime, masses=masses) - assert 1*u.dm/u.s < abs(zg_moonjupearth - zg0) < 1*u.m/u.s + assert 1 * u.dm / u.s < abs(zg_moonjupearth - zg0) < 1 * u.m / u.s # If all masses are zero, redshift should be 0 as well. - masses['sun'] = 0*u.kg + masses["sun"] = 0 * u.kg assert someloc.gravitational_redshift(sometime, masses=masses) == 0 with pytest.raises(KeyError): - someloc.gravitational_redshift(sometime, bodies=('saturn',)) + someloc.gravitational_redshift(sometime, bodies=("saturn",)) with pytest.raises(u.UnitsError): - masses = {'sun': constants.G*constants.M_sun, - 'jupiter': constants.G*constants.M_jup, - 'moon': 1*u.km, # wrong units! - 'earth': constants.G*constants.M_earth} + masses = { + "sun": constants.G * constants.M_sun, + "jupiter": constants.G * constants.M_jup, + "moon": 1 * u.km, # wrong units! + "earth": constants.G * constants.M_earth, + } someloc.gravitational_redshift(sometime, masses=masses) def test_read_only_input(): - lon = np.array([80., 440.]) * u.deg - lat = np.array([45.]) * u.deg + lon = np.array([80.0, 440.0]) * u.deg + lat = np.array([45.0]) * u.deg lon.flags.writeable = lat.flags.writeable = False loc = EarthLocation.from_geodetic(lon=lon, lat=lat) assert quantity_allclose(loc[1].x, loc[0].x) @@ -407,5 +416,5 @@ def test_read_only_input(): def test_info(): EarthLocation._get_site_registry(force_builtin=True) - greenwich = EarthLocation.of_site('greenwich') - assert str(greenwich.info).startswith('name = Royal Observatory Greenwich') + greenwich = EarthLocation.of_site("greenwich") + assert str(greenwich.info).startswith("name = Royal Observatory Greenwich") diff --git a/astropy/coordinates/tests/test_earth_orientation.py b/astropy/coordinates/tests/test_earth_orientation.py index f91f097d118..0f093516668 100644 --- a/astropy/coordinates/tests/test_earth_orientation.py +++ b/astropy/coordinates/tests/test_earth_orientation.py @@ -11,32 +11,54 @@ @pytest.fixture def tt_to_test(): - return Time('2022-08-25', scale='tt') + return Time("2022-08-25", scale="tt") -@pytest.mark.parametrize('algorithm, result', [(2006, 23.43633313804873), - (2000, 23.43634457995851), - (1980, 23.436346167704045)]) +@pytest.mark.parametrize( + "algorithm, result", + [(2006, 23.43633313804873), (2000, 23.43634457995851), (1980, 23.436346167704045)], +) def test_obliquity(tt_to_test, algorithm, result): - assert_allclose(earth_orientation.obliquity(tt_to_test.jd, algorithm=algorithm), - result, rtol=1e-13) + assert_allclose( + earth_orientation.obliquity(tt_to_test.jd, algorithm=algorithm), + result, + rtol=1e-13, + ) def test_precession_matrix_Capitaine(tt_to_test): - assert_allclose(earth_orientation.precession_matrix_Capitaine(tt_to_test, - tt_to_test + 12.345*u.yr), - np.array([[9.99995470e-01, -2.76086535e-03, -1.19936388e-03], - [2.76086537e-03, 9.99996189e-01, -1.64025847e-06], - [1.19936384e-03, -1.67103117e-06, 9.99999281e-01]]), rtol=1e-6) + assert_allclose( + earth_orientation.precession_matrix_Capitaine( + tt_to_test, tt_to_test + 12.345 * u.yr + ), + np.array( + [ + [9.99995470e-01, -2.76086535e-03, -1.19936388e-03], + [2.76086537e-03, +9.99996189e-01, -1.64025847e-06], + [1.19936384e-03, -1.67103117e-06, +9.99999281e-01], + ] + ), + rtol=1e-6, + ) def test_nutation_components2000B(tt_to_test): - assert_allclose(earth_orientation.nutation_components2000B(tt_to_test.jd), - (0.4090413775522035, -5.4418953539440996e-05, 3.176996651841667e-05), rtol=1e-13) + assert_allclose( + earth_orientation.nutation_components2000B(tt_to_test.jd), + (0.4090413775522035, -5.4418953539440996e-05, 3.176996651841667e-05), + rtol=1e-13, + ) def test_nutation_matrix(tt_to_test): - assert_allclose(earth_orientation.nutation_matrix(tt_to_test), - np.array([[9.99999999e-01, 4.99295268e-05, 2.16440489e-05], - [-4.99288392e-05, 9.99999998e-01, -3.17705068e-05], - [-2.16456351e-05, 3.17694261e-05, 9.99999999e-01]]), rtol=1e-6) + assert_allclose( + earth_orientation.nutation_matrix(tt_to_test), + np.array( + [ + [+9.99999999e-01, +4.99295268e-05, +2.16440489e-05], + [-4.99288392e-05, +9.99999998e-01, -3.17705068e-05], + [-2.16456351e-05, +3.17694261e-05, +9.99999999e-01], + ] + ), + rtol=1e-6, + ) diff --git a/astropy/coordinates/tests/test_erfa_astrom.py b/astropy/coordinates/tests/test_erfa_astrom.py index 81bd1797605..554b5566194 100644 --- a/astropy/coordinates/tests/test_erfa_astrom.py +++ b/astropy/coordinates/tests/test_erfa_astrom.py @@ -13,7 +13,6 @@ def test_science_state(): - assert erfa_astrom.get().__class__ is ErfaAstrom res = 300 * u.s @@ -26,11 +25,10 @@ def test_science_state(): # must be a subclass of BaseErfaAstrom with pytest.raises(TypeError): - erfa_astrom.set('foo') + erfa_astrom.set("foo") def test_warnings(): - with pytest.warns(AstropyWarning): with erfa_astrom.set(ErfaAstromInterpolator(9 * u.us)): pass @@ -47,7 +45,7 @@ def test_erfa_astrom(): lat=28.761584 * u.deg, height=2200 * u.m, ) - obstime = Time('2020-01-01T18:00') + np.linspace(0, 1, 100) * u.hour + obstime = Time("2020-01-01T18:00") + np.linspace(0, 1, 100) * u.hour altaz = AltAz(location=location, obstime=obstime) coord = SkyCoord(ra=83.63308333, dec=22.0145, unit=u.deg) @@ -66,9 +64,9 @@ def test_erfa_astrom(): def test_interpolation_nd(): - ''' + """ Test that the interpolation also works for nd-arrays - ''' + """ fact = EarthLocation( lon=-17.891105 * u.deg, @@ -79,16 +77,16 @@ def test_interpolation_nd(): interp_provider = ErfaAstromInterpolator(300 * u.s) provider = ErfaAstrom() - for shape in [tuple(), (1, ), (10, ), (3, 2), (2, 10, 5), (4, 5, 3, 2)]: + for shape in [tuple(), (1,), (10,), (3, 2), (2, 10, 5), (4, 5, 3, 2)]: # create obstimes of the desired shapes delta_t = np.linspace(0, 12, np.prod(shape, dtype=int)) * u.hour - obstime = (Time('2020-01-01T18:00') + delta_t).reshape(shape) + obstime = (Time("2020-01-01T18:00") + delta_t).reshape(shape) altaz = AltAz(location=fact, obstime=obstime) gcrs = GCRS(obstime=obstime) cirs = CIRS(obstime=obstime) - for frame, tcode in zip([altaz, cirs, gcrs], ['apio', 'apco', 'apcs']): + for frame, tcode in zip([altaz, cirs, gcrs], ["apio", "apco", "apcs"]): without_interp = getattr(provider, tcode)(frame) assert without_interp.shape == shape @@ -108,7 +106,7 @@ def test_interpolation_broadcasting(): coord = SkyCoord(rep) # 30 times over the space of 1 hours - times = Time('2020-01-01T20:00') + np.linspace(-0.5, 0.5, 30) * u.hour + times = Time("2020-01-01T20:00") + np.linspace(-0.5, 0.5, 30) * u.hour lst1 = EarthLocation( lon=-17.891498 * u.deg, diff --git a/astropy/coordinates/tests/test_finite_difference_velocities.py b/astropy/coordinates/tests/test_finite_difference_velocities.py index f7c8d0df263..411b5720a54 100644 --- a/astropy/coordinates/tests/test_finite_difference_velocities.py +++ b/astropy/coordinates/tests/test_finite_difference_velocities.py @@ -22,61 +22,82 @@ from astropy.time import Time from astropy.units import allclose as quantity_allclose -J2000 = Time('J2000') +J2000 = Time("J2000") -@pytest.mark.parametrize("dt, symmetric", [(1*u.second, True), - (1*u.year, True), - (1*u.second, False), - (1*u.year, False)]) +@pytest.mark.parametrize( + "dt, symmetric", + [ + (1 * u.second, True), + (1 * u.year, True), + (1 * u.second, False), + (1 * u.year, False), + ], +) def test_faux_lsr(dt, symmetric): class LSR2(LSR): obstime = TimeAttribute(default=J2000) - @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - ICRS, LSR2, finite_difference_dt=dt, - symmetric_finite_difference=symmetric) + @frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + ICRS, + LSR2, + finite_difference_dt=dt, + symmetric_finite_difference=symmetric, + ) def icrs_to_lsr(icrs_coo, lsr_frame): dt = lsr_frame.obstime - J2000 offset = lsr_frame.v_bary * dt.to(u.second) return lsr_frame.realize_frame(icrs_coo.data.without_differentials() + offset) - @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - LSR2, ICRS, finite_difference_dt=dt, - symmetric_finite_difference=symmetric) + @frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + LSR2, + ICRS, + finite_difference_dt=dt, + symmetric_finite_difference=symmetric, + ) def lsr_to_icrs(lsr_coo, icrs_frame): dt = lsr_coo.obstime - J2000 offset = lsr_coo.v_bary * dt.to(u.second) return icrs_frame.realize_frame(lsr_coo.data - offset) - ic = ICRS(ra=12.3*u.deg, dec=45.6*u.deg, distance=7.8*u.au, - pm_ra_cosdec=0*u.marcsec/u.yr, pm_dec=0*u.marcsec/u.yr, - radial_velocity=0*u.km/u.s) + ic = ICRS( + ra=12.3 * u.deg, + dec=45.6 * u.deg, + distance=7.8 * u.au, + pm_ra_cosdec=0 * u.marcsec / u.yr, + pm_dec=0 * u.marcsec / u.yr, + radial_velocity=0 * u.km / u.s, + ) lsrc = ic.transform_to(LSR2()) assert quantity_allclose(ic.cartesian.xyz, lsrc.cartesian.xyz) - idiff = ic.cartesian.differentials['s'] - ldiff = lsrc.cartesian.differentials['s'] - change = (ldiff.d_xyz - idiff.d_xyz).to(u.km/u.s) - totchange = np.sum(change**2)**0.5 - assert quantity_allclose(totchange, np.sum(lsrc.v_bary.d_xyz**2)**0.5) - - ic2 = ICRS(ra=120.3*u.deg, dec=45.6*u.deg, distance=7.8*u.au, - pm_ra_cosdec=0*u.marcsec/u.yr, pm_dec=10*u.marcsec/u.yr, - radial_velocity=1000*u.km/u.s) + idiff = ic.cartesian.differentials["s"] + ldiff = lsrc.cartesian.differentials["s"] + change = (ldiff.d_xyz - idiff.d_xyz).to(u.km / u.s) + totchange = np.sum(change**2) ** 0.5 + assert quantity_allclose(totchange, np.sum(lsrc.v_bary.d_xyz**2) ** 0.5) + + ic2 = ICRS( + ra=120.3 * u.deg, + dec=45.6 * u.deg, + distance=7.8 * u.au, + pm_ra_cosdec=0 * u.marcsec / u.yr, + pm_dec=10 * u.marcsec / u.yr, + radial_velocity=1000 * u.km / u.s, + ) lsrc2 = ic2.transform_to(LSR2()) ic2_roundtrip = lsrc2.transform_to(ICRS()) - tot = np.sum(lsrc2.cartesian.differentials['s'].d_xyz**2)**0.5 - assert np.abs(tot.to('km/s') - 1000*u.km/u.s) < 20*u.km/u.s + tot = np.sum(lsrc2.cartesian.differentials["s"].d_xyz ** 2) ** 0.5 + assert np.abs(tot.to("km/s") - 1000 * u.km / u.s) < 20 * u.km / u.s - assert quantity_allclose(ic2.cartesian.xyz, - ic2_roundtrip.cartesian.xyz) + assert quantity_allclose(ic2.cartesian.xyz, ic2_roundtrip.cartesian.xyz) def test_faux_fk5_galactic(): - from astropy.coordinates.builtin_frames.galactic_transforms import ( _gal_to_fk5, fk5_to_gal, @@ -85,27 +106,40 @@ def test_faux_fk5_galactic(): class Galactic2(Galactic): pass - dt = 1000*u.s + dt = 1000 * u.s - @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - FK5, Galactic2, finite_difference_dt=dt, - symmetric_finite_difference=True, - finite_difference_frameattr_name=None) + @frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + FK5, + Galactic2, + finite_difference_dt=dt, + symmetric_finite_difference=True, + finite_difference_frameattr_name=None, + ) def fk5_to_gal2(fk5_coo, gal_frame): trans = DynamicMatrixTransform(fk5_to_gal, FK5, Galactic2) return trans(fk5_coo, gal_frame) - @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, - Galactic2, ICRS, finite_difference_dt=dt, - symmetric_finite_difference=True, - finite_difference_frameattr_name=None) + @frame_transform_graph.transform( + FunctionTransformWithFiniteDifference, + Galactic2, + ICRS, + finite_difference_dt=dt, + symmetric_finite_difference=True, + finite_difference_frameattr_name=None, + ) def gal2_to_fk5(gal_coo, fk5_frame): trans = DynamicMatrixTransform(_gal_to_fk5, Galactic2, FK5) return trans(gal_coo, fk5_frame) - c1 = FK5(ra=150*u.deg, dec=-17*u.deg, radial_velocity=83*u.km/u.s, - pm_ra_cosdec=-41*u.mas/u.yr, pm_dec=16*u.mas/u.yr, - distance=150*u.pc) + c1 = FK5( + ra=150 * u.deg, + dec=-17 * u.deg, + radial_velocity=83 * u.km / u.s, + pm_ra_cosdec=-41 * u.mas / u.yr, + pm_dec=16 * u.mas / u.yr, + distance=150 * u.pc, + ) c2 = c1.transform_to(Galactic2()) c3 = c1.transform_to(Galactic()) @@ -115,22 +149,34 @@ def gal2_to_fk5(gal_coo, fk5_frame): def test_gcrs_diffs(): - time = Time('2017-01-01') + time = Time("2017-01-01") gf = GCRS(obstime=time) sung = get_sun(time) # should have very little vhelio # qtr-year off sun location should be the direction of ~ maximal vhelio - qtrsung = get_sun(time-.25*u.year) + qtrsung = get_sun(time - 0.25 * u.year) # now we use those essentially as directions where the velocities should # be either maximal or minimal - with or perpendiculat to Earh's orbit - msungr = CartesianRepresentation(-sung.cartesian.xyz).represent_as(SphericalRepresentation) - suni = ICRS(ra=msungr.lon, dec=msungr.lat, distance=100*u.au, - pm_ra_cosdec=0*u.marcsec/u.yr, pm_dec=0*u.marcsec/u.yr, - radial_velocity=0*u.km/u.s) - qtrsuni = ICRS(ra=qtrsung.ra, dec=qtrsung.dec, distance=100*u.au, - pm_ra_cosdec=0*u.marcsec/u.yr, pm_dec=0*u.marcsec/u.yr, - radial_velocity=0*u.km/u.s) + msungr = CartesianRepresentation(-sung.cartesian.xyz).represent_as( + SphericalRepresentation + ) + suni = ICRS( + ra=msungr.lon, + dec=msungr.lat, + distance=100 * u.au, + pm_ra_cosdec=0 * u.marcsec / u.yr, + pm_dec=0 * u.marcsec / u.yr, + radial_velocity=0 * u.km / u.s, + ) + qtrsuni = ICRS( + ra=qtrsung.ra, + dec=qtrsung.dec, + distance=100 * u.au, + pm_ra_cosdec=0 * u.marcsec / u.yr, + pm_dec=0 * u.marcsec / u.yr, + radial_velocity=0 * u.km / u.s, + ) # Now we transform those parallel- and perpendicular-to Earth's orbit # directions to GCRS, which should shift the velocity to either include @@ -141,25 +187,29 @@ def test_gcrs_diffs(): # should be high along the ecliptic-not-sun sun axis and # low along the sun axis - assert np.abs(qtrsung.radial_velocity) > 30*u.km/u.s - assert np.abs(qtrsung.radial_velocity) < 40*u.km/u.s - assert np.abs(sung.radial_velocity) < 1*u.km/u.s + assert np.abs(qtrsung.radial_velocity) > 30 * u.km / u.s + assert np.abs(qtrsung.radial_velocity) < 40 * u.km / u.s + assert np.abs(sung.radial_velocity) < 1 * u.km / u.s suni2 = sung.transform_to(ICRS()) - assert np.all(np.abs(suni2.data.differentials['s'].d_xyz) < 3e-5*u.km/u.s) + assert np.all(np.abs(suni2.data.differentials["s"].d_xyz) < 3e-5 * u.km / u.s) qtrisun2 = qtrsung.transform_to(ICRS()) - assert np.all(np.abs(qtrisun2.data.differentials['s'].d_xyz) < 3e-5*u.km/u.s) + assert np.all(np.abs(qtrisun2.data.differentials["s"].d_xyz) < 3e-5 * u.km / u.s) def test_altaz_diffs(): - time = Time('J2015') + np.linspace(-1, 1, 1000)*u.day - loc = get_builtin_sites()['greenwich'] + time = Time("J2015") + np.linspace(-1, 1, 1000) * u.day + loc = get_builtin_sites()["greenwich"] aa = AltAz(obstime=time, location=loc) - icoo = ICRS(np.zeros(time.shape)*u.deg, 10*u.deg, 100*u.au, - pm_ra_cosdec=np.zeros(time.shape)*u.marcsec/u.yr, - pm_dec=0*u.marcsec/u.yr, - radial_velocity=0*u.km/u.s) + icoo = ICRS( + np.zeros(time.shape) * u.deg, + 10 * u.deg, + 100 * u.au, + pm_ra_cosdec=np.zeros(time.shape) * u.marcsec / u.yr, + pm_dec=0 * u.marcsec / u.yr, + radial_velocity=0 * u.km / u.s, + ) acoo = icoo.transform_to(aa) @@ -167,45 +217,56 @@ def test_altaz_diffs(): # more than the rotation speed of the Earth - some excess is expected # because the orbit also shifts the RV, but it should be pretty small # over this short a time. - assert np.ptp(acoo.radial_velocity)/2 < (2*np.pi*constants.R_earth/u.day)*1.2 # MAGIC NUMBER + assert ( + np.ptp(acoo.radial_velocity) / 2 < (2 * np.pi * constants.R_earth / u.day) * 1.2 + ) # MAGIC NUMBER - cdiff = acoo.data.differentials['s'].represent_as(CartesianDifferential, - acoo.data) + cdiff = acoo.data.differentials["s"].represent_as(CartesianDifferential, acoo.data) # The "total" velocity should be > c, because the *tangential* velocity # isn't a True velocity, but rather an induced velocity due to the Earth's # rotation at a distance of 100 AU - assert np.all(np.sum(cdiff.d_xyz**2, axis=0)**0.5 > constants.c) + assert np.all(np.sum(cdiff.d_xyz**2, axis=0) ** 0.5 > constants.c) _xfail = pytest.mark.xfail -@pytest.mark.parametrize('distance', [1000*u.au, - 10*u.pc, - pytest.param(10*u.kpc, marks=_xfail), - pytest.param(100*u.kpc, marks=_xfail)]) - # TODO: make these not fail when the - # finite-difference numerical stability - # is improved +@pytest.mark.parametrize( + "distance", + [ + 1000 * u.au, + 10 * u.pc, + pytest.param(10 * u.kpc, marks=_xfail), + pytest.param(100 * u.kpc, marks=_xfail), + ], +) +# TODO: make these not fail when the +# finite-difference numerical stability +# is improved def test_numerical_limits(distance): """ Tests the numerical stability of the default settings for the finite difference transformation calculation. This is *known* to fail for at >~1kpc, but this may be improved in future versions. """ - time = Time('J2017') + np.linspace(-.5, .5, 100)*u.year - - icoo = ICRS(ra=0*u.deg, dec=10*u.deg, distance=distance, - pm_ra_cosdec=0*u.marcsec/u.yr, pm_dec=0*u.marcsec/u.yr, - radial_velocity=0*u.km/u.s) + time = Time("J2017") + np.linspace(-0.5, 0.5, 100) * u.year + + icoo = ICRS( + ra=0 * u.deg, + dec=10 * u.deg, + distance=distance, + pm_ra_cosdec=0 * u.marcsec / u.yr, + pm_dec=0 * u.marcsec / u.yr, + radial_velocity=0 * u.km / u.s, + ) gcoo = icoo.transform_to(GCRS(obstime=time)) - rv = gcoo.radial_velocity.to('km/s') + rv = gcoo.radial_velocity.to("km/s") # if its a lot bigger than this - ~the maximal velocity shift along # the direction above with a small allowance for noise - finite-difference # rounding errors have ruined the calculation - assert np.ptp(rv) < 65*u.km/u.s + assert np.ptp(rv) < 65 * u.km / u.s def diff_info_plot(frame, time): @@ -217,18 +278,24 @@ def diff_info_plot(frame, time): from matplotlib import pyplot as plt fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 12)) - ax1.plot_date(time.plot_date, frame.data.differentials['s'].d_xyz.to(u.km/u.s).T, fmt='-') - ax1.legend(['x', 'y', 'z']) + ax1.plot_date( + time.plot_date, frame.data.differentials["s"].d_xyz.to(u.km / u.s).T, fmt="-" + ) + ax1.legend(["x", "y", "z"]) - ax2.plot_date(time.plot_date, np.sum(frame.data.differentials['s'].d_xyz.to(u.km/u.s)**2, axis=0)**0.5, fmt='-') - ax2.set_title('total') + ax2.plot_date( + time.plot_date, + np.sum(frame.data.differentials["s"].d_xyz.to(u.km / u.s) ** 2, axis=0) ** 0.5, + fmt="-", + ) + ax2.set_title("total") - sd = frame.data.differentials['s'].represent_as(SphericalDifferential, frame.data) + sd = frame.data.differentials["s"].represent_as(SphericalDifferential, frame.data) - ax3.plot_date(time.plot_date, sd.d_distance.to(u.km/u.s), fmt='-') - ax3.set_title('radial') + ax3.plot_date(time.plot_date, sd.d_distance.to(u.km / u.s), fmt="-") + ax3.set_title("radial") - ax4.plot_date(time.plot_date, sd.d_lat.to(u.marcsec/u.yr), fmt='-', label='lat') - ax4.plot_date(time.plot_date, sd.d_lon.to(u.marcsec/u.yr), fmt='-', label='lon') + ax4.plot_date(time.plot_date, sd.d_lat.to(u.marcsec / u.yr), fmt="-", label="lat") + ax4.plot_date(time.plot_date, sd.d_lon.to(u.marcsec / u.yr), fmt="-", label="lon") return fig diff --git a/astropy/coordinates/tests/test_formatting.py b/astropy/coordinates/tests/test_formatting.py index df87019da33..a77dc51fa8c 100644 --- a/astropy/coordinates/tests/test_formatting.py +++ b/astropy/coordinates/tests/test_formatting.py @@ -15,125 +15,124 @@ def test_to_string_precision(): angle = Angle(-1.23456789, unit=u.degree) - assert angle.to_string(precision=3) == '-1d14m04.444s' - assert angle.to_string(precision=1) == '-1d14m04.4s' - assert angle.to_string(precision=0) == '-1d14m04s' + assert angle.to_string(precision=3) == "-1d14m04.444s" + assert angle.to_string(precision=1) == "-1d14m04.4s" + assert angle.to_string(precision=0) == "-1d14m04s" angle2 = Angle(-1.23456789, unit=u.hourangle) - assert angle2.to_string(precision=3, unit=u.hour) == '-1h14m04.444s' - assert angle2.to_string(precision=1, unit=u.hour) == '-1h14m04.4s' - assert angle2.to_string(precision=0, unit=u.hour) == '-1h14m04s' + assert angle2.to_string(precision=3, unit=u.hour) == "-1h14m04.444s" + assert angle2.to_string(precision=1, unit=u.hour) == "-1h14m04.4s" + assert angle2.to_string(precision=0, unit=u.hour) == "-1h14m04s" # Regression test for #7141 angle3 = Angle(-0.5, unit=u.degree) - assert angle3.to_string(precision=0, fields=3) == '-0d30m00s' - assert angle3.to_string(precision=0, fields=2) == '-0d30m' - assert angle3.to_string(precision=0, fields=1) == '-1d' + assert angle3.to_string(precision=0, fields=3) == "-0d30m00s" + assert angle3.to_string(precision=0, fields=2) == "-0d30m" + assert angle3.to_string(precision=0, fields=1) == "-1d" def test_to_string_decimal(): - # There are already some tests in test_api.py, but this is a regression # test for the bug in issue #1323 which caused decimal formatting to not # work - angle1 = Angle(2., unit=u.degree) + angle1 = Angle(2.0, unit=u.degree) - assert angle1.to_string(decimal=True, precision=3) == '2.000' - assert angle1.to_string(decimal=True, precision=1) == '2.0' - assert angle1.to_string(decimal=True, precision=0) == '2' + assert angle1.to_string(decimal=True, precision=3) == "2.000" + assert angle1.to_string(decimal=True, precision=1) == "2.0" + assert angle1.to_string(decimal=True, precision=0) == "2" - angle2 = Angle(3., unit=u.hourangle) + angle2 = Angle(3.0, unit=u.hourangle) - assert angle2.to_string(decimal=True, precision=3) == '3.000' - assert angle2.to_string(decimal=True, precision=1) == '3.0' - assert angle2.to_string(decimal=True, precision=0) == '3' + assert angle2.to_string(decimal=True, precision=3) == "3.000" + assert angle2.to_string(decimal=True, precision=1) == "3.0" + assert angle2.to_string(decimal=True, precision=0) == "3" - angle3 = Angle(4., unit=u.radian) + angle3 = Angle(4.0, unit=u.radian) - assert angle3.to_string(decimal=True, precision=3) == '4.000' - assert angle3.to_string(decimal=True, precision=1) == '4.0' - assert angle3.to_string(decimal=True, precision=0) == '4' + assert angle3.to_string(decimal=True, precision=3) == "4.000" + assert angle3.to_string(decimal=True, precision=1) == "4.0" + assert angle3.to_string(decimal=True, precision=0) == "4" - with pytest.raises(ValueError, match='sexagesimal notation'): - angle3.to_string(decimal=True, sep='abc') + with pytest.raises(ValueError, match="sexagesimal notation"): + angle3.to_string(decimal=True, sep="abc") def test_to_string_formats(): a = Angle(1.113355, unit=u.deg) - latex_str = r'$1^\circ06{}^\prime48.078{}^{\prime\prime}$' - assert a.to_string(format='latex') == latex_str - assert a.to_string(format='latex_inline') == latex_str - assert a.to_string(format='unicode') == '1°06′48.078″' + latex_str = r"$1^\circ06{}^\prime48.078{}^{\prime\prime}$" + assert a.to_string(format="latex") == latex_str + assert a.to_string(format="latex_inline") == latex_str + assert a.to_string(format="unicode") == "1°06′48.078″" a = Angle(1.113355, unit=u.hour) - latex_str = r'$1^{\mathrm{h}}06^{\mathrm{m}}48.078^{\mathrm{s}}$' - assert a.to_string(format='latex') == latex_str - assert a.to_string(format='latex_inline') == latex_str - assert a.to_string(format='unicode') == '1ʰ06ᵐ48.078ˢ' + latex_str = r"$1^{\mathrm{h}}06^{\mathrm{m}}48.078^{\mathrm{s}}$" + assert a.to_string(format="latex") == latex_str + assert a.to_string(format="latex_inline") == latex_str + assert a.to_string(format="unicode") == "1ʰ06ᵐ48.078ˢ" a = Angle(1.113355, unit=u.radian) - assert a.to_string(format='latex') == r'$1.11336\mathrm{rad}$' - assert a.to_string(format='latex_inline') == r'$1.11336\mathrm{rad}$' - assert a.to_string(format='unicode') == '1.11336rad' + assert a.to_string(format="latex") == r"$1.11336\mathrm{rad}$" + assert a.to_string(format="latex_inline") == r"$1.11336\mathrm{rad}$" + assert a.to_string(format="unicode") == "1.11336rad" def test_to_string_decimal_formats(): - angle1 = Angle(2., unit=u.degree) + angle1 = Angle(2.0, unit=u.degree) - assert angle1.to_string(decimal=True, format='generic') == '2deg' - assert angle1.to_string(decimal=True, format='latex') == '$2\\mathrm{{}^{\\circ}}$' - assert angle1.to_string(decimal=True, format='unicode') == '2°' + assert angle1.to_string(decimal=True, format="generic") == "2deg" + assert angle1.to_string(decimal=True, format="latex") == "$2\\mathrm{{}^{\\circ}}$" + assert angle1.to_string(decimal=True, format="unicode") == "2°" - angle2 = Angle(3., unit=u.hourangle) - assert angle2.to_string(decimal=True, format='generic') == '3hourangle' - assert angle2.to_string(decimal=True, format='latex') == '$3\\mathrm{{}^{h}}$' - assert angle2.to_string(decimal=True, format='unicode') == '3ʰ' + angle2 = Angle(3.0, unit=u.hourangle) + assert angle2.to_string(decimal=True, format="generic") == "3hourangle" + assert angle2.to_string(decimal=True, format="latex") == "$3\\mathrm{{}^{h}}$" + assert angle2.to_string(decimal=True, format="unicode") == "3ʰ" - angle3 = Angle(4., unit=u.radian) + angle3 = Angle(4.0, unit=u.radian) - assert angle3.to_string(decimal=True, format='generic') == '4rad' - assert angle3.to_string(decimal=True, format='latex') == '$4\\mathrm{rad}$' - assert angle3.to_string(decimal=True, format='unicode') == '4rad' + assert angle3.to_string(decimal=True, format="generic") == "4rad" + assert angle3.to_string(decimal=True, format="latex") == "$4\\mathrm{rad}$" + assert angle3.to_string(decimal=True, format="unicode") == "4rad" - with pytest.raises(ValueError, match='Unknown format'): - angle3.to_string(decimal=True, format='myformat') + with pytest.raises(ValueError, match="Unknown format"): + angle3.to_string(decimal=True, format="myformat") def test_to_string_fields(): a = Angle(1.113355, unit=u.deg) - assert a.to_string(fields=1) == r'1d' - assert a.to_string(fields=2) == r'1d07m' - assert a.to_string(fields=3) == r'1d06m48.078s' + assert a.to_string(fields=1) == r"1d" + assert a.to_string(fields=2) == r"1d07m" + assert a.to_string(fields=3) == r"1d06m48.078s" def test_to_string_padding(): a = Angle(0.5653, unit=u.deg) - assert a.to_string(unit='deg', sep=':', pad=True) == r'00:33:55.08' + assert a.to_string(unit="deg", sep=":", pad=True) == r"00:33:55.08" # Test to make sure negative angles are padded correctly a = Angle(-0.5653, unit=u.deg) - assert a.to_string(unit='deg', sep=':', pad=True) == r'-00:33:55.08' + assert a.to_string(unit="deg", sep=":", pad=True) == r"-00:33:55.08" def test_sexagesimal_rounding_up(): a = Angle(359.999999999999, unit=u.deg) - assert a.to_string(precision=None) == '360d00m00s' - assert a.to_string(precision=4) == '360d00m00.0000s' - assert a.to_string(precision=5) == '360d00m00.00000s' - assert a.to_string(precision=6) == '360d00m00.000000s' - assert a.to_string(precision=7) == '360d00m00.0000000s' - assert a.to_string(precision=8) == '360d00m00.00000000s' - assert a.to_string(precision=9) == '359d59m59.999999996s' + assert a.to_string(precision=None) == "360d00m00s" + assert a.to_string(precision=4) == "360d00m00.0000s" + assert a.to_string(precision=5) == "360d00m00.00000s" + assert a.to_string(precision=6) == "360d00m00.000000s" + assert a.to_string(precision=7) == "360d00m00.0000000s" + assert a.to_string(precision=8) == "360d00m00.00000000s" + assert a.to_string(precision=9) == "359d59m59.999999996s" a = Angle(3.999999, unit=u.deg) - assert a.to_string(fields=2, precision=None) == '4d00m' - assert a.to_string(fields=2, precision=1) == '4d00m' - assert a.to_string(fields=2, precision=5) == '4d00m' - assert a.to_string(fields=1, precision=1) == '4d' - assert a.to_string(fields=1, precision=5) == '4d' + assert a.to_string(fields=2, precision=None) == "4d00m" + assert a.to_string(fields=2, precision=1) == "4d00m" + assert a.to_string(fields=2, precision=5) == "4d00m" + assert a.to_string(fields=1, precision=1) == "4d" + assert a.to_string(fields=1, precision=5) == "4d" def test_to_string_scalar(): @@ -148,19 +147,19 @@ def test_to_string_radian_with_precision(): """ # Check that specifying the precision works - a = Angle(3., unit=u.rad) - assert a.to_string(precision=3, sep='fromunit') == '3.000rad' + a = Angle(3.0, unit=u.rad) + assert a.to_string(precision=3, sep="fromunit") == "3.000rad" def test_sexagesimal_round_down(): a1 = Angle(1, u.deg).to(u.hourangle) a2 = Angle(2, u.deg) - assert a1.to_string() == '0h04m00s' - assert a2.to_string() == '2d00m00s' + assert a1.to_string() == "0h04m00s" + assert a2.to_string() == "2d00m00s" def test_to_string_fields_colon(): a = Angle(1.113355, unit=u.deg) - assert a.to_string(fields=2, sep=':') == '1:07' - assert a.to_string(fields=3, sep=':') == '1:06:48.078' - assert a.to_string(fields=1, sep=':') == '1' + assert a.to_string(fields=2, sep=":") == "1:07" + assert a.to_string(fields=3, sep=":") == "1:06:48.078" + assert a.to_string(fields=1, sep=":") == "1" diff --git a/astropy/coordinates/tests/test_frames.py b/astropy/coordinates/tests/test_frames.py index 459066082d0..8424b49a84e 100644 --- a/astropy/coordinates/tests/test_frames.py +++ b/astropy/coordinates/tests/test_frames.py @@ -55,14 +55,13 @@ def teardown_function(func): def test_frame_attribute_descriptor(): """Unit tests of the Attribute descriptor.""" + class TestAttributes: attr_none = Attribute() attr_2 = Attribute(default=2) - attr_3_attr2 = Attribute(default=3, secondary_attribute='attr_2') - attr_none_attr2 = Attribute(default=None, secondary_attribute='attr_2') - attr_none_nonexist = Attribute( - default=None, secondary_attribute='nonexist' - ) + attr_3_attr2 = Attribute(default=3, secondary_attribute="attr_2") + attr_none_attr2 = Attribute(default=None, secondary_attribute="attr_2") + attr_none_nonexist = Attribute(default=None, secondary_attribute="nonexist") t = TestAttributes() @@ -89,30 +88,30 @@ class TestAttributes: # Make sure setting values via public attribute fails with pytest.raises(AttributeError) as err: t.attr_none = 5 - assert 'Cannot set frame attribute' in str(err.value) + assert "Cannot set frame attribute" in str(err.value) def test_frame_subclass_attribute_descriptor(): """Unit test of the attribute descriptors in subclasses.""" - _EQUINOX_B1980 = Time('B1980', scale='tai') + _EQUINOX_B1980 = Time("B1980", scale="tai") class MyFK4(FK4): # equinox inherited from FK4, obstime overridden, and newattr is new obstime = TimeAttribute(default=_EQUINOX_B1980) - newattr = Attribute(default='newattr') + newattr = Attribute(default="newattr") mfk4 = MyFK4() - assert mfk4.equinox.value == 'B1950.000' - assert mfk4.obstime.value == 'B1980.000' - assert mfk4.newattr == 'newattr' + assert mfk4.equinox.value == "B1950.000" + assert mfk4.obstime.value == "B1980.000" + assert mfk4.newattr == "newattr" with pytest.warns(AstropyDeprecationWarning): - assert set(mfk4.get_frame_attr_names()) == {'equinox', 'obstime', 'newattr'} + assert set(mfk4.get_frame_attr_names()) == {"equinox", "obstime", "newattr"} - mfk4 = MyFK4(equinox='J1980.0', obstime='J1990.0', newattr='world') - assert mfk4.equinox.value == 'J1980.000' - assert mfk4.obstime.value == 'J1990.000' - assert mfk4.newattr == 'world' + mfk4 = MyFK4(equinox="J1980.0", obstime="J1990.0", newattr="world") + assert mfk4.equinox.value == "J1980.000" + assert mfk4.obstime.value == "J1990.000" + assert mfk4.newattr == "world" def test_frame_multiple_inheritance_attribute_descriptor(): @@ -132,8 +131,8 @@ class Frame3(Frame1, Frame2): pass assert len(Frame3.frame_attributes) == 2 - assert 'attr1' in Frame3.frame_attributes - assert 'attr2' in Frame3.frame_attributes + assert "attr1" in Frame3.frame_attributes + assert "attr2" in Frame3.frame_attributes # In case the same attribute exists in both frames, the one from the # left-most class in the MRO should take precedence @@ -144,19 +143,19 @@ class Frame4(BaseCoordinateFrame): class Frame5(Frame1, Frame4): pass - assert Frame5.frame_attributes['attr1'] is Frame1.frame_attributes['attr1'] - assert Frame5.frame_attributes['attr2'] is Frame4.frame_attributes['attr2'] + assert Frame5.frame_attributes["attr1"] is Frame1.frame_attributes["attr1"] + assert Frame5.frame_attributes["attr2"] is Frame4.frame_attributes["attr2"] def test_differentialattribute(): - # Test logic of passing input through to allowed class - vel = [1, 2, 3]*u.km/u.s + vel = [1, 2, 3] * u.km / u.s dif = r.CartesianDifferential(vel) class TestFrame(BaseCoordinateFrame): attrtest = DifferentialAttribute( - default=dif, allowed_classes=[r.CartesianDifferential]) + default=dif, allowed_classes=[r.CartesianDifferential] + ) frame1 = TestFrame() frame2 = TestFrame(attrtest=dif) @@ -168,8 +167,9 @@ class TestFrame(BaseCoordinateFrame): # This shouldn't work if there is more than one allowed class: class TestFrame2(BaseCoordinateFrame): attrtest = DifferentialAttribute( - default=dif, allowed_classes=[r.CartesianDifferential, - r.CylindricalDifferential]) + default=dif, + allowed_classes=[r.CartesianDifferential, r.CylindricalDifferential], + ) frame1 = TestFrame2() frame2 = TestFrame2(attrtest=dif) @@ -179,12 +179,12 @@ class TestFrame2(BaseCoordinateFrame): def test_create_data_frames(): # from repr - i1 = ICRS(r.SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc)) - i2 = ICRS(r.UnitSphericalRepresentation(lon=1*u.deg, lat=2*u.deg)) + i1 = ICRS(r.SphericalRepresentation(1 * u.deg, 2 * u.deg, 3 * u.kpc)) + i2 = ICRS(r.UnitSphericalRepresentation(lon=1 * u.deg, lat=2 * u.deg)) # from preferred name - i3 = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.kpc) - i4 = ICRS(ra=1*u.deg, dec=2*u.deg) + i3 = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=3 * u.kpc) + i4 = ICRS(ra=1 * u.deg, dec=2 * u.deg) assert i1.data.lat == i3.data.lat assert i1.data.lon == i3.data.lon @@ -199,109 +199,123 @@ def test_create_data_frames(): assert_allclose(i1.distance, i3.distance) with pytest.raises(AttributeError): - i1.ra = [11.]*u.deg + i1.ra = [11.0] * u.deg def test_create_orderered_data(): + TOL = 1e-10 * u.deg - TOL = 1e-10*u.deg + i = ICRS(1 * u.deg, 2 * u.deg) + assert (i.ra - 1 * u.deg) < TOL + assert (i.dec - 2 * u.deg) < TOL - i = ICRS(1*u.deg, 2*u.deg) - assert (i.ra - 1*u.deg) < TOL - assert (i.dec - 2*u.deg) < TOL + g = Galactic(1 * u.deg, 2 * u.deg) + assert (g.l - 1 * u.deg) < TOL + assert (g.b - 2 * u.deg) < TOL - g = Galactic(1*u.deg, 2*u.deg) - assert (g.l - 1*u.deg) < TOL - assert (g.b - 2*u.deg) < TOL - - a = AltAz(1*u.deg, 2*u.deg) - assert (a.az - 1*u.deg) < TOL - assert (a.alt - 2*u.deg) < TOL + a = AltAz(1 * u.deg, 2 * u.deg) + assert (a.az - 1 * u.deg) < TOL + assert (a.alt - 2 * u.deg) < TOL with pytest.raises(TypeError): - ICRS(1*u.deg, 2*u.deg, 1*u.deg, 2*u.deg) + ICRS(1 * u.deg, 2 * u.deg, 1 * u.deg, 2 * u.deg) with pytest.raises(TypeError): - sph = r.SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc) - ICRS(sph, 1*u.deg, 2*u.deg) + sph = r.SphericalRepresentation(1 * u.deg, 2 * u.deg, 3 * u.kpc) + ICRS(sph, 1 * u.deg, 2 * u.deg) def test_create_nodata_frames(): - i = ICRS() assert len(i.frame_attributes) == 0 f5 = FK5() - assert f5.equinox == FK5.get_frame_attr_defaults()['equinox'] + assert f5.equinox == FK5.get_frame_attr_defaults()["equinox"] f4 = FK4() - assert f4.equinox == FK4.get_frame_attr_defaults()['equinox'] + assert f4.equinox == FK4.get_frame_attr_defaults()["equinox"] # obstime is special because it's a property that uses equinox if obstime is not set - assert f4.obstime in (FK4.get_frame_attr_defaults()['obstime'], - FK4.get_frame_attr_defaults()['equinox']) + assert f4.obstime in ( + FK4.get_frame_attr_defaults()["obstime"], + FK4.get_frame_attr_defaults()["equinox"], + ) def test_no_data_nonscalar_frames(): - a1 = AltAz(obstime=Time('2012-01-01') + np.arange(10.) * u.day, - temperature=np.ones((3, 1)) * u.deg_C) + a1 = AltAz( + obstime=Time("2012-01-01") + np.arange(10.0) * u.day, + temperature=np.ones((3, 1)) * u.deg_C, + ) assert a1.obstime.shape == (3, 10) assert a1.temperature.shape == (3, 10) assert a1.shape == (3, 10) with pytest.raises(ValueError) as exc: - AltAz(obstime=Time('2012-01-01') + np.arange(10.) * u.day, - temperature=np.ones((3,)) * u.deg_C) - assert 'inconsistent shapes' in str(exc.value) + AltAz( + obstime=Time("2012-01-01") + np.arange(10.0) * u.day, + temperature=np.ones((3,)) * u.deg_C, + ) + assert "inconsistent shapes" in str(exc.value) def test_frame_repr(): i = ICRS() - assert repr(i) == '' + assert repr(i) == "" f5 = FK5() - assert repr(f5).startswith('') - assert repr(i3) == ('') + assert repr(i2) == "" + assert ( + repr(i3) + == "" + ) # try with arrays - i2 = ICRS(ra=[1.1, 2.1]*u.deg, dec=[2.1, 3.1]*u.deg) - i3 = ICRS(ra=[1.1, 2.1]*u.deg, dec=[-15.6, 17.1]*u.deg, distance=[11., 21.]*u.kpc) + i2 = ICRS(ra=[1.1, 2.1] * u.deg, dec=[2.1, 3.1] * u.deg) + i3 = ICRS( + ra=[1.1, 2.1] * u.deg, dec=[-15.6, 17.1] * u.deg, distance=[11.0, 21.0] * u.kpc + ) - assert repr(i2) == ('') + assert ( + repr(i2) == "" + ) - assert repr(i3) == ('') + assert ( + repr(i3) == "" + ) def test_frame_repr_vels(): - - i = ICRS(ra=1*u.deg, dec=2*u.deg, - pm_ra_cosdec=1*u.marcsec/u.yr, pm_dec=2*u.marcsec/u.yr) + i = ICRS( + ra=1 * u.deg, + dec=2 * u.deg, + pm_ra_cosdec=1 * u.marcsec / u.yr, + pm_dec=2 * u.marcsec / u.yr, + ) # unit comes out as mas/yr because of the preferred units defined in the # frame RepresentationMapping - assert repr(i) == ('') + assert ( + repr(i) == "" + ) def test_converting_units(): - # this is a regular expression that with split (see below) removes what's # the decimal point to fix rounding problems - rexrepr = re.compile(r'(.*?=\d\.).*?( .*?=\d\.).*?( .*)') + rexrepr = re.compile(r"(.*?=\d\.).*?( .*?=\d\.).*?( .*)") # Use values that aren't subject to rounding down to X.9999... - i2 = ICRS(ra=2.*u.deg, dec=2.*u.deg) - i2_many = ICRS(ra=[2., 4.]*u.deg, dec=[2., -8.1]*u.deg) + i2 = ICRS(ra=2.0 * u.deg, dec=2.0 * u.deg) + i2_many = ICRS(ra=[2.0, 4.0] * u.deg, dec=[2.0, -8.1] * u.deg) # converting from FK5 to ICRS and back changes the *internal* representation, # but it should still come out in the preferred form @@ -309,13 +323,13 @@ def test_converting_units(): i4 = i2.transform_to(FK5()).transform_to(ICRS()) i4_many = i2_many.transform_to(FK5()).transform_to(ICRS()) - ri2 = ''.join(rexrepr.split(repr(i2))) - ri4 = ''.join(rexrepr.split(repr(i4))) + ri2 = "".join(rexrepr.split(repr(i2))) + ri4 = "".join(rexrepr.split(repr(i4))) assert ri2 == ri4 assert i2.data.lon.unit != i4.data.lon.unit # Internal repr changed - ri2_many = ''.join(rexrepr.split(repr(i2_many))) - ri4_many = ''.join(rexrepr.split(repr(i4_many))) + ri2_many = "".join(rexrepr.split(repr(i2_many))) + ri4_many = "".join(rexrepr.split(repr(i4_many))) assert ri2_many == ri4_many assert i2_many.data.lon.unit != i4_many.data.lon.unit # Internal repr changed @@ -323,15 +337,17 @@ def test_converting_units(): # but that *shouldn't* hold if we turn off units for the representation class FakeICRS(ICRS): frame_specific_representation_info = { - 'spherical': [RepresentationMapping('lon', 'ra', u.hourangle), - RepresentationMapping('lat', 'dec', None), - RepresentationMapping('distance', 'distance')] # should fall back to default of None unit + "spherical": [ + RepresentationMapping("lon", "ra", u.hourangle), + RepresentationMapping("lat", "dec", None), + RepresentationMapping("distance", "distance"), + ] # should fall back to default of None unit } fi = FakeICRS(i4.data) - ri2 = ''.join(rexrepr.split(repr(i2))) - rfi = ''.join(rexrepr.split(repr(fi))) - rfi = re.sub('FakeICRS', 'ICRS', rfi) # Force frame name to match + ri2 = "".join(rexrepr.split(repr(i2))) + rfi = "".join(rexrepr.split(repr(fi))) + rfi = re.sub("FakeICRS", "ICRS", rfi) # Force frame name to match assert ri2 != rfi # the attributes should also get the right units @@ -343,74 +359,85 @@ class FakeICRS(ICRS): def test_representation_info(): - class NewICRS1(ICRS): frame_specific_representation_info = { r.SphericalRepresentation: [ - RepresentationMapping('lon', 'rara', u.hourangle), - RepresentationMapping('lat', 'decdec', u.degree), - RepresentationMapping('distance', 'distance', u.kpc)] + RepresentationMapping("lon", "rara", u.hourangle), + RepresentationMapping("lat", "decdec", u.degree), + RepresentationMapping("distance", "distance", u.kpc), + ] } - i1 = NewICRS1(rara=10*u.degree, decdec=-12*u.deg, distance=1000*u.pc, - pm_rara_cosdecdec=100*u.mas/u.yr, - pm_decdec=17*u.mas/u.yr, - radial_velocity=10*u.km/u.s) - assert allclose(i1.rara, 10*u.deg) + i1 = NewICRS1( + rara=10 * u.degree, + decdec=-12 * u.deg, + distance=1000 * u.pc, + pm_rara_cosdecdec=100 * u.mas / u.yr, + pm_decdec=17 * u.mas / u.yr, + radial_velocity=10 * u.km / u.s, + ) + assert allclose(i1.rara, 10 * u.deg) assert i1.rara.unit == u.hourangle - assert allclose(i1.decdec, -12*u.deg) - assert allclose(i1.distance, 1000*u.pc) + assert allclose(i1.decdec, -12 * u.deg) + assert allclose(i1.distance, 1000 * u.pc) assert i1.distance.unit == u.kpc - assert allclose(i1.pm_rara_cosdecdec, 100*u.mas/u.yr) - assert allclose(i1.pm_decdec, 17*u.mas/u.yr) + assert allclose(i1.pm_rara_cosdecdec, 100 * u.mas / u.yr) + assert allclose(i1.pm_decdec, 17 * u.mas / u.yr) # this should auto-set the names of UnitSpherical: - i1.set_representation_cls(r.UnitSphericalRepresentation, - s=r.UnitSphericalCosLatDifferential) - assert allclose(i1.rara, 10*u.deg) - assert allclose(i1.decdec, -12*u.deg) - assert allclose(i1.pm_rara_cosdecdec, 100*u.mas/u.yr) - assert allclose(i1.pm_decdec, 17*u.mas/u.yr) + i1.set_representation_cls( + r.UnitSphericalRepresentation, s=r.UnitSphericalCosLatDifferential + ) + assert allclose(i1.rara, 10 * u.deg) + assert allclose(i1.decdec, -12 * u.deg) + assert allclose(i1.pm_rara_cosdecdec, 100 * u.mas / u.yr) + assert allclose(i1.pm_decdec, 17 * u.mas / u.yr) # For backwards compatibility, we also support the string name in the # representation info dictionary: class NewICRS2(ICRS): frame_specific_representation_info = { - 'spherical': [ - RepresentationMapping('lon', 'ang1', u.hourangle), - RepresentationMapping('lat', 'ang2', u.degree), - RepresentationMapping('distance', 'howfar', u.kpc)] + "spherical": [ + RepresentationMapping("lon", "ang1", u.hourangle), + RepresentationMapping("lat", "ang2", u.degree), + RepresentationMapping("distance", "howfar", u.kpc), + ] } - i2 = NewICRS2(ang1=10*u.degree, ang2=-12*u.deg, howfar=1000*u.pc) - assert allclose(i2.ang1, 10*u.deg) + i2 = NewICRS2(ang1=10 * u.degree, ang2=-12 * u.deg, howfar=1000 * u.pc) + assert allclose(i2.ang1, 10 * u.deg) assert i2.ang1.unit == u.hourangle - assert allclose(i2.ang2, -12*u.deg) - assert allclose(i2.howfar, 1000*u.pc) + assert allclose(i2.ang2, -12 * u.deg) + assert allclose(i2.howfar, 1000 * u.pc) assert i2.howfar.unit == u.kpc # Test that the differential kwargs get overridden class NewICRS3(ICRS): frame_specific_representation_info = { r.SphericalCosLatDifferential: [ - RepresentationMapping('d_lon_coslat', 'pm_ang1', u.hourangle/u.year), - RepresentationMapping('d_lat', 'pm_ang2'), - RepresentationMapping('d_distance', 'vlos', u.kpc/u.Myr)] + RepresentationMapping("d_lon_coslat", "pm_ang1", u.hourangle / u.year), + RepresentationMapping("d_lat", "pm_ang2"), + RepresentationMapping("d_distance", "vlos", u.kpc / u.Myr), + ] } - i3 = NewICRS3(lon=10*u.degree, lat=-12*u.deg, distance=1000*u.pc, - pm_ang1=1*u.mas/u.yr, pm_ang2=2*u.mas/u.yr, - vlos=100*u.km/u.s) - assert allclose(i3.pm_ang1, 1*u.mas/u.yr) - assert i3.pm_ang1.unit == u.hourangle/u.year - assert allclose(i3.pm_ang2, 2*u.mas/u.yr) - assert allclose(i3.vlos, 100*u.km/u.s) - assert i3.vlos.unit == u.kpc/u.Myr + i3 = NewICRS3( + lon=10 * u.degree, + lat=-12 * u.deg, + distance=1000 * u.pc, + pm_ang1=1 * u.mas / u.yr, + pm_ang2=2 * u.mas / u.yr, + vlos=100 * u.km / u.s, + ) + assert allclose(i3.pm_ang1, 1 * u.mas / u.yr) + assert i3.pm_ang1.unit == u.hourangle / u.year + assert allclose(i3.pm_ang2, 2 * u.mas / u.yr) + assert allclose(i3.vlos, 100 * u.km / u.s) + assert i3.vlos.unit == u.kpc / u.Myr def test_realizing(): - - rep = r.SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc) + rep = r.SphericalRepresentation(1 * u.deg, 2 * u.deg, 3 * u.kpc) i = ICRS() i2 = i.realize_frame(rep) @@ -418,30 +445,28 @@ def test_realizing(): assert not i.has_data assert i2.has_data - f = FK5(equinox=Time('J2001')) + f = FK5(equinox=Time("J2001")) f2 = f.realize_frame(rep) assert not f.has_data assert f2.has_data assert f2.equinox == f.equinox - assert f2.equinox != FK5.get_frame_attr_defaults()['equinox'] + assert f2.equinox != FK5.get_frame_attr_defaults()["equinox"] # Check that a nicer error message is returned: - with pytest.raises(TypeError) as excinfo: + with pytest.raises( + TypeError, match="Class passed as data instead of a representation" + ): f.realize_frame(f.representation_type) - assert ('Class passed as data instead of a representation' in - excinfo.value.args[0]) - def test_replicating(): - - i = ICRS(ra=[1]*u.deg, dec=[2]*u.deg) + i = ICRS(ra=[1] * u.deg, dec=[2] * u.deg) icopy = i.replicate(copy=True) irepl = i.replicate(copy=False) - i.data._lat[:] = 0*u.deg + i.data._lat[:] = 0 * u.deg assert np.all(i.data.lat == irepl.data.lat) assert np.all(i.data.lat != icopy.data.lat) @@ -449,8 +474,8 @@ def test_replicating(): assert i.has_data assert not iclone.has_data - aa = AltAz(alt=1*u.deg, az=2*u.deg, obstime=Time('J2000')) - aaclone = aa.replicate_without_data(obstime=Time('J2001')) + aa = AltAz(alt=1 * u.deg, az=2 * u.deg, obstime=Time("J2000")) + aaclone = aa.replicate_without_data(obstime=Time("J2001")) assert not aaclone.has_data assert aa.obstime != aaclone.obstime assert aa.pressure == aaclone.pressure @@ -458,9 +483,9 @@ def test_replicating(): def test_getitem(): - rep = r.SphericalRepresentation( - [1, 2, 3]*u.deg, [4, 5, 6]*u.deg, [7, 8, 9]*u.kpc) + [1, 2, 3] * u.deg, [4, 5, 6] * u.deg, [7, 8, 9] * u.kpc + ) i = ICRS(rep) assert len(i.ra) == 3 @@ -478,7 +503,7 @@ def test_transform(): actually test all the builtin transforms themselves are accurate. """ - i = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg) + i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) f = i.transform_to(FK5()) i2 = f.transform_to(ICRS()) @@ -487,13 +512,13 @@ def test_transform(): assert_allclose(i.ra, i2.ra) assert_allclose(i.dec, i2.dec) - i = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[5, 6]*u.kpc) + i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[5, 6] * u.kpc) f = i.transform_to(FK5()) i2 = f.transform_to(ICRS()) assert i2.data.__class__ != r.UnitSphericalRepresentation - f = FK5(ra=1*u.deg, dec=2*u.deg, equinox=Time('J2001')) + f = FK5(ra=1 * u.deg, dec=2 * u.deg, equinox=Time("J2001")) f4 = f.transform_to(FK4()) f4_2 = f.transform_to(FK4(equinox=f.equinox)) @@ -502,13 +527,13 @@ def test_transform(): assert f4_2.equinox == f.equinox # make sure self-transforms also work - i = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg) + i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) i2 = i.transform_to(ICRS()) assert_allclose(i.ra, i2.ra) assert_allclose(i.dec, i2.dec) - f = FK5(ra=1*u.deg, dec=2*u.deg, equinox=Time('J2001')) + f = FK5(ra=1 * u.deg, dec=2 * u.deg, equinox=Time("J2001")) f2 = f.transform_to(FK5()) # default equinox, so should be *different* assert f2.equinox == FK5().equinox with pytest.raises(AssertionError): @@ -517,7 +542,7 @@ def test_transform(): assert_allclose(f.dec, f2.dec) # finally, check Galactic round-tripping - i1 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg) + i1 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) i2 = i1.transform_to(Galactic()).transform_to(ICRS()) assert_allclose(i1.ra, i2.ra) @@ -526,24 +551,23 @@ def test_transform(): def test_transform_to_nonscalar_nodata_frame(): # https://github.com/astropy/astropy/pull/5254#issuecomment-241592353 - times = Time('2016-08-23') + np.linspace(0, 10, 12)*u.day - coo1 = ICRS(ra=[[0.], [10.], [20.]]*u.deg, - dec=[[-30.], [30.], [60.]]*u.deg) + times = Time("2016-08-23") + np.linspace(0, 10, 12) * u.day + coo1 = ICRS( + ra=[[0.0], [10.0], [20.0]] * u.deg, dec=[[-30.0], [30.0], [60.0]] * u.deg + ) coo2 = coo1.transform_to(FK5(equinox=times)) assert coo2.shape == (3, 12) def test_setitem_no_velocity(): - """Test different flavors of item setting for a Frame without a velocity. - - """ - obstime = 'B1955' - sc0 = FK4([1, 2]*u.deg, [3, 4]*u.deg, obstime=obstime) - sc2 = FK4([10, 20]*u.deg, [30, 40]*u.deg, obstime=obstime) + """Test different flavors of item setting for a Frame without a velocity.""" + obstime = "B1955" + sc0 = FK4([1, 2] * u.deg, [3, 4] * u.deg, obstime=obstime) + sc2 = FK4([10, 20] * u.deg, [30, 40] * u.deg, obstime=obstime) sc1 = sc0.copy() sc1_repr = repr(sc1) - assert 'representation' in sc1.cache + assert "representation" in sc1.cache sc1[1] = sc2[0] assert sc1.cache == {} assert repr(sc2) != sc1_repr @@ -551,7 +575,7 @@ def test_setitem_no_velocity(): assert np.allclose(sc1.ra.to_value(u.deg), [1, 10]) assert np.allclose(sc1.dec.to_value(u.deg), [3, 30]) assert sc1.obstime == sc2.obstime - assert sc1.name == 'fk4' + assert sc1.name == "fk4" sc1 = sc0.copy() sc1[:] = sc2[0] @@ -581,13 +605,19 @@ def test_setitem_no_velocity(): def test_setitem_velocities(): - """Test different flavors of item setting for a Frame with a velocity. - - """ - sc0 = FK4([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 2]*u.km/u.s, - obstime='B1950') - sc2 = FK4([10, 20]*u.deg, [30, 40]*u.deg, radial_velocity=[10, 20]*u.km/u.s, - obstime='B1950') + """Test different flavors of item setting for a Frame with a velocity.""" + sc0 = FK4( + [1, 2] * u.deg, + [3, 4] * u.deg, + radial_velocity=[1, 2] * u.km / u.s, + obstime="B1950", + ) + sc2 = FK4( + [10, 20] * u.deg, + [30, 40] * u.deg, + radial_velocity=[10, 20] * u.km / u.s, + obstime="B1950", + ) sc1 = sc0.copy() sc1[1] = sc2[0] @@ -595,7 +625,7 @@ def test_setitem_velocities(): assert np.allclose(sc1.dec.to_value(u.deg), [3, 30]) assert np.allclose(sc1.radial_velocity.to_value(u.km / u.s), [1, 10]) assert sc1.obstime == sc2.obstime - assert sc1.name == 'fk4' + assert sc1.name == "fk4" sc1 = sc0.copy() sc1[:] = sc2[0] @@ -617,69 +647,85 @@ def test_setitem_velocities(): def test_setitem_exceptions(): - - obstime = 'B1950' - sc0 = FK4([1, 2]*u.deg, [3, 4]*u.deg) - sc2 = FK4([10, 20]*u.deg, [30, 40]*u.deg, obstime=obstime) + obstime = "B1950" + sc0 = FK4([1, 2] * u.deg, [3, 4] * u.deg) + sc2 = FK4([10, 20] * u.deg, [30, 40] * u.deg, obstime=obstime) sc1 = Galactic(sc0.ra, sc0.dec) - with pytest.raises(TypeError, match='can only set from object of same class: ' - 'Galactic vs. FK4'): + with pytest.raises( + TypeError, match="can only set from object of same class: Galactic vs. FK4" + ): sc1[0] = sc2[0] - sc1 = FK4(sc0.ra, sc0.dec, obstime='B2001') - with pytest.raises(ValueError, match='can only set frame item from an equivalent frame'): + sc1 = FK4(sc0.ra, sc0.dec, obstime="B2001") + with pytest.raises( + ValueError, match="can only set frame item from an equivalent frame" + ): sc1[0] = sc2[0] sc1 = FK4(sc0.ra[0], sc0.dec[0], obstime=obstime) - with pytest.raises(TypeError, match="scalar 'FK4' frame object does not support " - 'item assignment'): + with pytest.raises( + TypeError, match="scalar 'FK4' frame object does not support item assignment" + ): sc1[0] = sc2[0] sc1 = FK4(obstime=obstime) - with pytest.raises(ValueError, match='cannot set frame which has no data'): + with pytest.raises(ValueError, match="cannot set frame which has no data"): sc1[0] = sc2[0] - sc1 = FK4(sc0.ra, sc0.dec, obstime=[obstime, 'B1980']) - with pytest.raises(ValueError, match='can only set frame item from an equivalent frame'): + sc1 = FK4(sc0.ra, sc0.dec, obstime=[obstime, "B1980"]) + with pytest.raises( + ValueError, match="can only set frame item from an equivalent frame" + ): sc1[0] = sc2[0] # Wrong shape - sc1 = FK4([sc0.ra], [sc0.dec], obstime=[obstime, 'B1980']) - with pytest.raises(ValueError, match='can only set frame item from an equivalent frame'): + sc1 = FK4([sc0.ra], [sc0.dec], obstime=[obstime, "B1980"]) + with pytest.raises( + ValueError, match="can only set frame item from an equivalent frame" + ): sc1[0] = sc2[0] def test_sep(): - - i1 = ICRS(ra=0*u.deg, dec=1*u.deg) - i2 = ICRS(ra=0*u.deg, dec=2*u.deg) + i1 = ICRS(ra=0 * u.deg, dec=1 * u.deg) + i2 = ICRS(ra=0 * u.deg, dec=2 * u.deg) sep = i1.separation(i2) - assert_allclose(sep.deg, 1.) + assert_allclose(sep.deg, 1.0) - i3 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[5, 6]*u.kpc) - i4 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[4, 5]*u.kpc) + i3 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[5, 6] * u.kpc) + i4 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[4, 5] * u.kpc) sep3d = i3.separation_3d(i4) - assert_allclose(sep3d.to(u.kpc), np.array([1, 1])*u.kpc) + assert_allclose(sep3d.to(u.kpc), np.array([1, 1]) * u.kpc) # check that it works even with velocities - i5 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[5, 6]*u.kpc, - pm_ra_cosdec=[1, 2]*u.mas/u.yr, pm_dec=[3, 4]*u.mas/u.yr, - radial_velocity=[5, 6]*u.km/u.s) - i6 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[7, 8]*u.kpc, - pm_ra_cosdec=[1, 2]*u.mas/u.yr, pm_dec=[3, 4]*u.mas/u.yr, - radial_velocity=[5, 6]*u.km/u.s) + i5 = ICRS( + ra=[1, 2] * u.deg, + dec=[3, 4] * u.deg, + distance=[5, 6] * u.kpc, + pm_ra_cosdec=[1, 2] * u.mas / u.yr, + pm_dec=[3, 4] * u.mas / u.yr, + radial_velocity=[5, 6] * u.km / u.s, + ) + i6 = ICRS( + ra=[1, 2] * u.deg, + dec=[3, 4] * u.deg, + distance=[7, 8] * u.kpc, + pm_ra_cosdec=[1, 2] * u.mas / u.yr, + pm_dec=[3, 4] * u.mas / u.yr, + radial_velocity=[5, 6] * u.km / u.s, + ) sep3d = i5.separation_3d(i6) - assert_allclose(sep3d.to(u.kpc), np.array([2, 2])*u.kpc) + assert_allclose(sep3d.to(u.kpc), np.array([2, 2]) * u.kpc) # 3d separations of dimensionless distances should still work - i7 = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.one) - i8 = ICRS(ra=1*u.deg, dec=2*u.deg, distance=4*u.one) + i7 = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=3 * u.one) + i8 = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=4 * u.one) sep3d = i7.separation_3d(i8) - assert_allclose(sep3d, 1*u.one) + assert_allclose(sep3d, 1 * u.one) # but should fail with non-dimensionless with pytest.raises(ValueError): @@ -691,24 +737,24 @@ def test_time_inputs(): Test validation and conversion of inputs for equinox and obstime attributes. """ - c = FK4(1 * u.deg, 2 * u.deg, equinox='J2001.5', obstime='2000-01-01 12:00:00') - assert c.equinox == Time('J2001.5') - assert c.obstime == Time('2000-01-01 12:00:00') + c = FK4(1 * u.deg, 2 * u.deg, equinox="J2001.5", obstime="2000-01-01 12:00:00") + assert c.equinox == Time("J2001.5") + assert c.obstime == Time("2000-01-01 12:00:00") with pytest.raises(ValueError) as err: c = FK4(1 * u.deg, 2 * u.deg, equinox=1.5) - assert 'Invalid time input' in str(err.value) + assert "Invalid time input" in str(err.value) with pytest.raises(ValueError) as err: - c = FK4(1 * u.deg, 2 * u.deg, obstime='hello') - assert 'Invalid time input' in str(err.value) + c = FK4(1 * u.deg, 2 * u.deg, obstime="hello") + assert "Invalid time input" in str(err.value) # A vector time should work if the shapes match, but we don't automatically # broadcast the basic data (just like time). - FK4([1, 2] * u.deg, [2, 3] * u.deg, obstime=['J2000', 'J2001']) + FK4([1, 2] * u.deg, [2, 3] * u.deg, obstime=["J2000", "J2001"]) with pytest.raises(ValueError) as err: - FK4(1 * u.deg, 2 * u.deg, obstime=['J2000', 'J2001']) - assert 'shape' in str(err.value) + FK4(1 * u.deg, 2 * u.deg, obstime=["J2000", "J2001"]) + assert "shape" in str(err.value) def test_is_frame_attr_default(): @@ -716,52 +762,60 @@ def test_is_frame_attr_default(): Check that the `is_frame_attr_default` machinery works as expected """ - c1 = FK5(ra=1*u.deg, dec=1*u.deg) - c2 = FK5(ra=1*u.deg, dec=1*u.deg, equinox=FK5.get_frame_attr_defaults()['equinox']) - c3 = FK5(ra=1*u.deg, dec=1*u.deg, equinox=Time('J2001.5')) + c1 = FK5(ra=1 * u.deg, dec=1 * u.deg) + c2 = FK5( + ra=1 * u.deg, dec=1 * u.deg, equinox=FK5.get_frame_attr_defaults()["equinox"] + ) + c3 = FK5(ra=1 * u.deg, dec=1 * u.deg, equinox=Time("J2001.5")) assert c1.equinox == c2.equinox assert c1.equinox != c3.equinox - assert c1.is_frame_attr_default('equinox') - assert not c2.is_frame_attr_default('equinox') - assert not c3.is_frame_attr_default('equinox') + assert c1.is_frame_attr_default("equinox") + assert not c2.is_frame_attr_default("equinox") + assert not c3.is_frame_attr_default("equinox") - c4 = c1.realize_frame(r.UnitSphericalRepresentation(3*u.deg, 4*u.deg)) - c5 = c2.realize_frame(r.UnitSphericalRepresentation(3*u.deg, 4*u.deg)) + c4 = c1.realize_frame(r.UnitSphericalRepresentation(3 * u.deg, 4 * u.deg)) + c5 = c2.realize_frame(r.UnitSphericalRepresentation(3 * u.deg, 4 * u.deg)) - assert c4.is_frame_attr_default('equinox') - assert not c5.is_frame_attr_default('equinox') + assert c4.is_frame_attr_default("equinox") + assert not c5.is_frame_attr_default("equinox") def test_altaz_attributes(): - aa = AltAz(1*u.deg, 2*u.deg) + aa = AltAz(1 * u.deg, 2 * u.deg) assert aa.obstime is None assert aa.location is None - aa2 = AltAz(1*u.deg, 2*u.deg, obstime='J2000') - assert aa2.obstime == Time('J2000') + aa2 = AltAz(1 * u.deg, 2 * u.deg, obstime="J2000") + assert aa2.obstime == Time("J2000") - aa3 = AltAz(1*u.deg, 2*u.deg, location=EarthLocation(0*u.deg, 0*u.deg, 0*u.m)) + aa3 = AltAz( + 1 * u.deg, 2 * u.deg, location=EarthLocation(0 * u.deg, 0 * u.deg, 0 * u.m) + ) assert isinstance(aa3.location, EarthLocation) def test_hadec_attributes(): - hd = HADec(1*u.hourangle, 2*u.deg) - assert hd.ha == 1.*u.hourangle - assert hd.dec == 2*u.deg + hd = HADec(1 * u.hourangle, 2 * u.deg) + assert hd.ha == 1.0 * u.hourangle + assert hd.dec == 2 * u.deg assert hd.obstime is None assert hd.location is None - hd2 = HADec(23*u.hourangle, -2*u.deg, obstime='J2000', - location=EarthLocation(0*u.deg, 0*u.deg, 0*u.m)) - assert_allclose(hd2.ha, -1*u.hourangle) - assert hd2.dec == -2*u.deg - assert hd2.obstime == Time('J2000') + hd2 = HADec( + 23 * u.hourangle, + -2 * u.deg, + obstime="J2000", + location=EarthLocation(0 * u.deg, 0 * u.deg, 0 * u.m), + ) + assert_allclose(hd2.ha, -1 * u.hourangle) + assert hd2.dec == -2 * u.deg + assert hd2.obstime == Time("J2000") assert isinstance(hd2.location, EarthLocation) sr = hd2.represent_as(r.SphericalRepresentation) - assert_allclose(sr.lon, -1*u.hourangle) + assert_allclose(sr.lon, -1 * u.hourangle) def test_representation(): @@ -770,7 +824,7 @@ def test_representation(): """ # Create the frame object. - icrs = ICRS(ra=1*u.deg, dec=1*u.deg) + icrs = ICRS(ra=1 * u.deg, dec=1 * u.deg) data = icrs.data # Create some representation objects. @@ -788,10 +842,10 @@ def test_representation(): assert icrs.data == data # Testing that an ICRS object in CartesianRepresentation must not have spherical attributes. - for attr in ('ra', 'dec', 'distance'): + for attr in ("ra", "dec", "distance"): with pytest.raises(AttributeError) as err: getattr(icrs, attr) - assert 'object has no attribute' in str(err.value) + assert "object has no attribute" in str(err.value) # Testing when `_representation` set to `CylindricalRepresentation`. icrs.representation_type = r.CylindricalRepresentation @@ -800,7 +854,7 @@ def test_representation(): assert icrs.data == data # Testing setter input using text argument for spherical. - icrs.representation_type = 'spherical' + icrs.representation_type = "spherical" assert icrs.representation_type is r.SphericalRepresentation assert icrs_spher.lat == icrs.dec @@ -809,13 +863,13 @@ def test_representation(): assert icrs.data == data # Testing that an ICRS object in SphericalRepresentation must not have cartesian attributes. - for attr in ('x', 'y', 'z'): + for attr in ("x", "y", "z"): with pytest.raises(AttributeError) as err: getattr(icrs, attr) - assert 'object has no attribute' in str(err.value) + assert "object has no attribute" in str(err.value) # Testing setter input using text argument for cylindrical. - icrs.representation_type = 'cylindrical' + icrs.representation_type = "cylindrical" assert icrs.representation_type is r.CylindricalRepresentation assert icrs_cyl.rho == icrs.rho @@ -824,25 +878,24 @@ def test_representation(): assert icrs.data == data # Testing that an ICRS object in CylindricalRepresentation must not have spherical attributes. - for attr in ('ra', 'dec', 'distance'): + for attr in ("ra", "dec", "distance"): with pytest.raises(AttributeError) as err: getattr(icrs, attr) - assert 'object has no attribute' in str(err.value) + assert "object has no attribute" in str(err.value) with pytest.raises(ValueError) as err: - icrs.representation_type = 'WRONG' - assert 'but must be a BaseRepresentation class' in str(err.value) + icrs.representation_type = "WRONG" + assert "but must be a BaseRepresentation class" in str(err.value) with pytest.raises(ValueError) as err: icrs.representation_type = ICRS - assert 'but must be a BaseRepresentation class' in str(err.value) + assert "but must be a BaseRepresentation class" in str(err.value) def test_represent_as(): + icrs = ICRS(ra=1 * u.deg, dec=1 * u.deg) - icrs = ICRS(ra=1*u.deg, dec=1*u.deg) - - cart1 = icrs.represent_as('cartesian') + cart1 = icrs.represent_as("cartesian") cart2 = icrs.represent_as(r.CartesianRepresentation) cart1.x == cart2.x @@ -850,17 +903,22 @@ def test_represent_as(): cart1.z == cart2.z # now try with velocities - icrs = ICRS(ra=0*u.deg, dec=0*u.deg, distance=10*u.kpc, - pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, - radial_velocity=1*u.km/u.s) + icrs = ICRS( + ra=0 * u.deg, + dec=0 * u.deg, + distance=10 * u.kpc, + pm_ra_cosdec=0 * u.mas / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=1 * u.km / u.s, + ) # single string - rep2 = icrs.represent_as('cylindrical') + rep2 = icrs.represent_as("cylindrical") assert isinstance(rep2, r.CylindricalRepresentation) - assert isinstance(rep2.differentials['s'], r.CylindricalDifferential) + assert isinstance(rep2.differentials["s"], r.CylindricalDifferential) # single class with positional in_frame_units, verify that warning raised - with pytest.warns(AstropyWarning, match='argument position') as w: + with pytest.warns(AstropyWarning, match="argument position") as w: icrs.represent_as(r.CylindricalRepresentation, False) assert len(w) == 1 @@ -874,35 +932,33 @@ def test_represent_as(): # assert isinstance(rep2.differentials['s'], r.SphericalCosLatDifferential) with pytest.raises(ValueError): - icrs.represent_as('odaigahara') + icrs.represent_as("odaigahara") def test_shorthand_representations(): - - rep = r.CartesianRepresentation([1, 2, 3]*u.pc) - dif = r.CartesianDifferential([1, 2, 3]*u.km/u.s) + rep = r.CartesianRepresentation([1, 2, 3] * u.pc) + dif = r.CartesianDifferential([1, 2, 3] * u.km / u.s) rep = rep.with_differentials(dif) icrs = ICRS(rep) cyl = icrs.cylindrical assert isinstance(cyl, r.CylindricalRepresentation) - assert isinstance(cyl.differentials['s'], r.CylindricalDifferential) + assert isinstance(cyl.differentials["s"], r.CylindricalDifferential) sph = icrs.spherical assert isinstance(sph, r.SphericalRepresentation) - assert isinstance(sph.differentials['s'], r.SphericalDifferential) + assert isinstance(sph.differentials["s"], r.SphericalDifferential) sph = icrs.sphericalcoslat assert isinstance(sph, r.SphericalRepresentation) - assert isinstance(sph.differentials['s'], r.SphericalCosLatDifferential) + assert isinstance(sph.differentials["s"], r.SphericalCosLatDifferential) def test_equal(): - - obstime = 'B1955' - sc1 = FK4([1, 2]*u.deg, [3, 4]*u.deg, obstime=obstime) - sc2 = FK4([1, 20]*u.deg, [3, 4]*u.deg, obstime=obstime) + obstime = "B1955" + sc1 = FK4([1, 2] * u.deg, [3, 4] * u.deg, obstime=obstime) + sc2 = FK4([1, 20] * u.deg, [3, 4] * u.deg, obstime=obstime) # Compare arrays and scalars eq = sc1 == sc2 @@ -919,8 +975,8 @@ def test_equal(): assert np.all(ne == [False, True]) # With diff only in velocity - sc1 = FK4([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 2]*u.km/u.s) - sc2 = FK4([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 20]*u.km/u.s) + sc1 = FK4([1, 2] * u.deg, [3, 4] * u.deg, radial_velocity=[1, 2] * u.km / u.s) + sc2 = FK4([1, 2] * u.deg, [3, 4] * u.deg, radial_velocity=[1, 20] * u.km / u.s) eq = sc1 == sc2 ne = sc1 != sc2 @@ -930,60 +986,79 @@ def test_equal(): assert isinstance(v := (sc1[0] != sc2[0]), (bool, np.bool_)) and not v assert (FK4() == ICRS()) is False - assert (FK4() == FK4(obstime='J1999')) is False + assert (FK4() == FK4(obstime="J1999")) is False def test_equal_exceptions(): - # Shape mismatch - sc1 = FK4([1, 2, 3]*u.deg, [3, 4, 5]*u.deg) - with pytest.raises(ValueError, match='cannot compare: shape mismatch'): + sc1 = FK4([1, 2, 3] * u.deg, [3, 4, 5] * u.deg) + with pytest.raises(ValueError, match="cannot compare: shape mismatch"): sc1 == sc1[:2] # Different representation_type - sc1 = FK4(1, 2, 3, representation_type='cartesian') - sc2 = FK4(1*u.deg, 2*u.deg, 2, representation_type='spherical') - with pytest.raises(TypeError, match='cannot compare: objects must have same ' - 'class: CartesianRepresentation vs. SphericalRepresentation'): + sc1 = FK4(1, 2, 3, representation_type="cartesian") + sc2 = FK4(1 * u.deg, 2 * u.deg, 2, representation_type="spherical") + with pytest.raises( + TypeError, + match=( + "cannot compare: objects must have same " + "class: CartesianRepresentation vs. SphericalRepresentation" + ), + ): sc1 == sc2 # Different differential type - sc1 = FK4(1*u.deg, 2*u.deg, radial_velocity=1*u.km/u.s) - sc2 = FK4(1*u.deg, 2*u.deg, pm_ra_cosdec=1*u.mas/u.yr, pm_dec=1*u.mas/u.yr) - with pytest.raises(TypeError, match='cannot compare: objects must have same ' - 'class: RadialDifferential vs. UnitSphericalCosLatDifferential'): + sc1 = FK4(1 * u.deg, 2 * u.deg, radial_velocity=1 * u.km / u.s) + sc2 = FK4( + 1 * u.deg, 2 * u.deg, pm_ra_cosdec=1 * u.mas / u.yr, pm_dec=1 * u.mas / u.yr + ) + with pytest.raises( + TypeError, + match=( + "cannot compare: objects must have same " + "class: RadialDifferential vs. UnitSphericalCosLatDifferential" + ), + ): sc1 == sc2 # Different frame attribute - sc1 = FK5(1*u.deg, 2*u.deg) - sc2 = FK5(1*u.deg, 2*u.deg, equinox='J1999') - with pytest.raises(TypeError, match=r'cannot compare: objects must have equivalent ' - r'frames: ' - r'vs. '): + sc1 = FK5(1 * u.deg, 2 * u.deg) + sc2 = FK5(1 * u.deg, 2 * u.deg, equinox="J1999") + with pytest.raises( + TypeError, + match=r"cannot compare: objects must have equivalent " + r"frames: " + r"vs. ", + ): sc1 == sc2 # Different frame - sc1 = FK4(1*u.deg, 2*u.deg) - sc2 = FK5(1*u.deg, 2*u.deg, equinox='J2000') - with pytest.raises(TypeError, match='cannot compare: objects must have equivalent ' - r'frames: ' - r'vs. '): + sc1 = FK4(1 * u.deg, 2 * u.deg) + sc2 = FK5(1 * u.deg, 2 * u.deg, equinox="J2000") + with pytest.raises( + TypeError, + match="cannot compare: objects must have equivalent " + r"frames: " + r"vs. ", + ): sc1 == sc2 - sc1 = FK4(1*u.deg, 2*u.deg) + sc1 = FK4(1 * u.deg, 2 * u.deg) sc2 = FK4() - with pytest.raises(ValueError, match='cannot compare: one frame has data and ' - 'the other does not'): + with pytest.raises( + ValueError, match="cannot compare: one frame has data and the other does not" + ): sc1 == sc2 - with pytest.raises(ValueError, match='cannot compare: one frame has data and ' - 'the other does not'): + with pytest.raises( + ValueError, match="cannot compare: one frame has data and the other does not" + ): sc2 == sc1 def test_dynamic_attrs(): - c = ICRS(1*u.deg, 2*u.deg) - assert 'ra' in dir(c) - assert 'dec' in dir(c) + c = ICRS(1 * u.deg, 2 * u.deg) + assert "ra" in dir(c) + assert "dec" in dir(c) with pytest.raises(AttributeError) as err: c.blahblah @@ -998,37 +1073,33 @@ def test_dynamic_attrs(): def test_nodata_error(): - i = ICRS() with pytest.raises(ValueError) as excinfo: i.data - assert 'does not have associated data' in str(excinfo.value) + assert "does not have associated data" in str(excinfo.value) def test_len0_data(): - - i = ICRS([]*u.deg, []*u.deg) + i = ICRS([] * u.deg, [] * u.deg) assert i.has_data repr(i) def test_quantity_attributes(): - # make sure we can create a GCRS frame with valid inputs - GCRS(obstime='J2002', obsgeoloc=[1, 2, 3]*u.km, obsgeovel=[4, 5, 6]*u.km/u.s) + GCRS(obstime="J2002", obsgeoloc=[1, 2, 3] * u.km, obsgeovel=[4, 5, 6] * u.km / u.s) # make sure it fails for invalid lovs or vels with pytest.raises(TypeError): GCRS(obsgeoloc=[1, 2, 3]) # no unit with pytest.raises(u.UnitsError): - GCRS(obsgeoloc=[1, 2, 3]*u.km/u.s) # incorrect unit + GCRS(obsgeoloc=[1, 2, 3] * u.km / u.s) # incorrect unit with pytest.raises(ValueError): - GCRS(obsgeoloc=[1, 3]*u.km) # incorrect shape + GCRS(obsgeoloc=[1, 3] * u.km) # incorrect shape def test_quantity_attribute_default(): - # The default default (yes) is None: class MyCoord(BaseCoordinateFrame): someval = QuantityAttribute(unit=u.deg) @@ -1036,60 +1107,62 @@ class MyCoord(BaseCoordinateFrame): frame = MyCoord() assert frame.someval is None - frame = MyCoord(someval=15*u.deg) - assert u.isclose(frame.someval, 15*u.deg) + frame = MyCoord(someval=15 * u.deg) + assert u.isclose(frame.someval, 15 * u.deg) # This should work if we don't explicitly pass in a unit, but we pass in a # default value with a unit class MyCoord2(BaseCoordinateFrame): - someval = QuantityAttribute(15*u.deg) + someval = QuantityAttribute(15 * u.deg) frame = MyCoord2() - assert u.isclose(frame.someval, 15*u.deg) + assert u.isclose(frame.someval, 15 * u.deg) # Since here no shape was given, we can set to any shape we like. - frame = MyCoord2(someval=np.ones(3)*u.deg) + frame = MyCoord2(someval=np.ones(3) * u.deg) assert frame.someval.shape == (3,) - assert np.all(frame.someval == 1*u.deg) + assert np.all(frame.someval == 1 * u.deg) # We should also be able to insist on a given shape. class MyCoord3(BaseCoordinateFrame): someval = QuantityAttribute(unit=u.arcsec, shape=(3,)) - frame = MyCoord3(someval=np.ones(3)*u.deg) + frame = MyCoord3(someval=np.ones(3) * u.deg) assert frame.someval.shape == (3,) assert frame.someval.unit == u.arcsec - assert u.allclose(frame.someval.value, 3600.) + assert u.allclose(frame.someval.value, 3600.0) # The wrong shape raises. - with pytest.raises(ValueError, match='shape'): - MyCoord3(someval=1.*u.deg) + with pytest.raises(ValueError, match="shape"): + MyCoord3(someval=1.0 * u.deg) # As does the wrong unit. with pytest.raises(u.UnitsError): - MyCoord3(someval=np.ones(3)*u.m) + MyCoord3(someval=np.ones(3) * u.m) # We are allowed a short-cut for zero. frame0 = MyCoord3(someval=0) assert frame0.someval.shape == (3,) assert frame0.someval.unit == u.arcsec - assert np.all(frame0.someval.value == 0.) + assert np.all(frame0.someval.value == 0.0) # But not if it has the wrong shape. - with pytest.raises(ValueError, match='shape'): + with pytest.raises(ValueError, match="shape"): MyCoord3(someval=np.zeros(2)) # This should fail, if we don't pass in a default or a unit with pytest.raises(ValueError): + class MyCoord(BaseCoordinateFrame): someval = QuantityAttribute() def test_eloc_attributes(): - - el = EarthLocation(lon=12.3*u.deg, lat=45.6*u.deg, height=1*u.km) - it = ITRS(r.SphericalRepresentation(lon=12.3*u.deg, lat=45.6*u.deg, distance=1*u.km)) - gc = GCRS(ra=12.3*u.deg, dec=45.6*u.deg, distance=6375*u.km) + el = EarthLocation(lon=12.3 * u.deg, lat=45.6 * u.deg, height=1 * u.km) + it = ITRS( + r.SphericalRepresentation(lon=12.3 * u.deg, lat=45.6 * u.deg, distance=1 * u.km) + ) + gc = GCRS(ra=12.3 * u.deg, dec=45.6 * u.deg, distance=6375 * u.km) el1 = AltAz(location=el).location assert isinstance(el1, EarthLocation) @@ -1108,7 +1181,7 @@ def test_eloc_attributes(): # the center of the Earth assert not allclose(el2.lat, it.spherical.lat) assert allclose(el2.lon, it.spherical.lon) - assert el2.height < -6000*u.km + assert el2.height < -6000 * u.km el3 = AltAz(location=gc).location # GCRS inputs implicitly get transformed to ITRS and then onto @@ -1116,12 +1189,12 @@ def test_eloc_attributes(): assert isinstance(el3, EarthLocation) assert not allclose(el3.lat, gc.dec) assert not allclose(el3.lon, gc.ra) - assert np.abs(el3.height) < 500*u.km + assert np.abs(el3.height) < 500 * u.km def test_equivalent_frames(): i = ICRS() - i2 = ICRS(1*u.deg, 2*u.deg) + i2 = ICRS(1 * u.deg, 2 * u.deg) assert i.is_equivalent_frame(i) assert i.is_equivalent_frame(i2) with pytest.raises(TypeError): @@ -1130,10 +1203,10 @@ def test_equivalent_frames(): assert i2.is_equivalent_frame(SkyCoord(i2)) f0 = FK5() # this J2000 is TT - f1 = FK5(equinox='J2000') - f2 = FK5(1*u.deg, 2*u.deg, equinox='J2000') - f3 = FK5(equinox='J2010') - f4 = FK4(equinox='J2010') + f1 = FK5(equinox="J2000") + f2 = FK5(1 * u.deg, 2 * u.deg, equinox="J2000") + f3 = FK5(equinox="J2010") + f4 = FK4(equinox="J2010") assert f1.is_equivalent_frame(f1) assert not i.is_equivalent_frame(f1) @@ -1143,7 +1216,7 @@ def test_equivalent_frames(): assert not f3.is_equivalent_frame(f4) aa1 = AltAz() - aa2 = AltAz(obstime='J2010') + aa2 = AltAz(obstime="J2010") assert aa2.is_equivalent_frame(aa2) assert not aa1.is_equivalent_frame(i) @@ -1151,15 +1224,20 @@ def test_equivalent_frames(): def test_equivalent_frame_coordinateattribute(): - class FrameWithCoordinateAttribute(BaseCoordinateFrame): coord_attr = CoordinateAttribute(HCRS) # These frames should not be considered equivalent f0 = FrameWithCoordinateAttribute() - f1 = FrameWithCoordinateAttribute(coord_attr=HCRS(1*u.deg, 2*u.deg, obstime='J2000')) - f2 = FrameWithCoordinateAttribute(coord_attr=HCRS(3*u.deg, 4*u.deg, obstime='J2000')) - f3 = FrameWithCoordinateAttribute(coord_attr=HCRS(1*u.deg, 2*u.deg, obstime='J2001')) + f1 = FrameWithCoordinateAttribute( + coord_attr=HCRS(1 * u.deg, 2 * u.deg, obstime="J2000") + ) + f2 = FrameWithCoordinateAttribute( + coord_attr=HCRS(3 * u.deg, 4 * u.deg, obstime="J2000") + ) + f3 = FrameWithCoordinateAttribute( + coord_attr=HCRS(1 * u.deg, 2 * u.deg, obstime="J2001") + ) assert not f0.is_equivalent_frame(f1) assert not f1.is_equivalent_frame(f0) @@ -1175,7 +1253,6 @@ class FrameWithCoordinateAttribute(BaseCoordinateFrame): def test_equivalent_frame_locationattribute(): - class FrameWithLocationAttribute(BaseCoordinateFrame): loc_attr = EarthLocationAttribute() @@ -1198,7 +1275,9 @@ def test_representation_subclass(): # Normally when instantiating a frame without a distance the frame will try # and use UnitSphericalRepresentation internally instead of # SphericalRepresentation. - frame = FK5(representation_type=r.SphericalRepresentation, ra=32 * u.deg, dec=20 * u.deg) + frame = FK5( + representation_type=r.SphericalRepresentation, ra=32 * u.deg, dec=20 * u.deg + ) assert type(frame._data) == r.UnitSphericalRepresentation assert frame.representation_type == r.SphericalRepresentation @@ -1207,14 +1286,18 @@ def test_representation_subclass(): class NewSphericalRepresentation(r.SphericalRepresentation): attr_classes = r.SphericalRepresentation.attr_classes - frame = FK5(representation_type=NewSphericalRepresentation, lon=32 * u.deg, lat=20 * u.deg) + frame = FK5( + representation_type=NewSphericalRepresentation, lon=32 * u.deg, lat=20 * u.deg + ) assert type(frame._data) == r.UnitSphericalRepresentation assert frame.representation_type == NewSphericalRepresentation # A similar issue then happened in __repr__ with subclasses of # SphericalRepresentation. - assert repr(frame) == ("") + assert ( + repr(frame) + == "" + ) # A more subtle issue is when specifying a custom # UnitSphericalRepresentation subclass for the data and @@ -1226,8 +1309,10 @@ class NewUnitSphericalRepresentation(r.UnitSphericalRepresentation): def __repr__(self): return "" - frame = FK5(NewUnitSphericalRepresentation(lon=32 * u.deg, lat=20 * u.deg), - representation_type=NewSphericalRepresentation) + frame = FK5( + NewUnitSphericalRepresentation(lon=32 * u.deg, lat=20 * u.deg), + representation_type=NewSphericalRepresentation, + ) assert repr(frame) == "" @@ -1238,7 +1323,7 @@ def test_getitem_representation(): from data representation. """ c = ICRS([1, 1] * u.deg, [2, 2] * u.deg) - c.representation_type = 'cartesian' + c.representation_type = "cartesian" assert c[0].representation_type is r.CartesianRepresentation @@ -1252,7 +1337,7 @@ def test_component_error_useful(): with pytest.raises(ValueError) as excinfo: i.ra - assert 'does not have associated data' in str(excinfo.value) + assert "does not have associated data" in str(excinfo.value) with pytest.raises(AttributeError) as excinfo1: i.foobar @@ -1263,52 +1348,49 @@ def test_component_error_useful(): def test_cache_clear(): - - i = ICRS(1*u.deg, 2*u.deg) + i = ICRS(1 * u.deg, 2 * u.deg) # Add an in frame units version of the rep to the cache. repr(i) - assert len(i.cache['representation']) == 2 + assert len(i.cache["representation"]) == 2 i.cache.clear() - assert len(i.cache['representation']) == 0 + assert len(i.cache["representation"]) == 0 def test_inplace_array(): - - i = ICRS([[1, 2], [3, 4]]*u.deg, [[10, 20], [30, 40]]*u.deg) + i = ICRS([[1, 2], [3, 4]] * u.deg, [[10, 20], [30, 40]] * u.deg) # Add an in frame units version of the rep to the cache. repr(i) # Check that repr() has added a rep to the cache - assert len(i.cache['representation']) == 2 + assert len(i.cache["representation"]) == 2 # Modify the data - i.data.lon[:, 0] = [100, 200]*u.deg + i.data.lon[:, 0] = [100, 200] * u.deg # Clear the cache i.cache.clear() # This will use a second (potentially cached rep) - assert_allclose(i.ra, [[100, 2], [200, 4]]*u.deg) - assert_allclose(i.dec, [[10, 20], [30, 40]]*u.deg) + assert_allclose(i.ra, [[100, 2], [200, 4]] * u.deg) + assert_allclose(i.dec, [[10, 20], [30, 40]] * u.deg) def test_inplace_change(): - - i = ICRS(1*u.deg, 2*u.deg) + i = ICRS(1 * u.deg, 2 * u.deg) # Add an in frame units version of the rep to the cache. repr(i) # Check that repr() has added a rep to the cache - assert len(i.cache['representation']) == 2 + assert len(i.cache["representation"]) == 2 # Modify the data - i.data.lon[()] = 10*u.deg + i.data.lon[()] = 10 * u.deg # Clear the cache i.cache.clear() @@ -1319,11 +1401,11 @@ def test_inplace_change(): def test_representation_with_multiple_differentials(): - - dif1 = r.CartesianDifferential([1, 2, 3]*u.km/u.s) - dif2 = r.CartesianDifferential([1, 2, 3]*u.km/u.s**2) - rep = r.CartesianRepresentation([1, 2, 3]*u.pc, - differentials={'s': dif1, 's2': dif2}) + dif1 = r.CartesianDifferential([1, 2, 3] * u.km / u.s) + dif2 = r.CartesianDifferential([1, 2, 3] * u.km / u.s**2) + rep = r.CartesianRepresentation( + [1, 2, 3] * u.pc, differentials={"s": dif1, "s2": dif2} + ) # check warning is raised for a scalar with pytest.raises(ValueError): @@ -1348,25 +1430,27 @@ def test_missing_component_error_names(): assert "missing 1 required positional argument: 'dec'" in str(e.value) with pytest.raises(TypeError) as e: - ICRS(ra=150*u.deg, dec=-11*u.deg, - pm_ra=100*u.mas/u.yr, pm_dec=10*u.mas/u.yr) + ICRS( + ra=150 * u.deg, + dec=-11 * u.deg, + pm_ra=100 * u.mas / u.yr, + pm_dec=10 * u.mas / u.yr, + ) assert "pm_ra_cosdec" in str(e.value) def test_non_spherical_representation_unit_creation(unitphysics): # noqa: F811 - class PhysicsICRS(ICRS): default_representation = r.PhysicsSphericalRepresentation - pic = PhysicsICRS(phi=1*u.deg, theta=25*u.deg, r=1*u.kpc) + pic = PhysicsICRS(phi=1 * u.deg, theta=25 * u.deg, r=1 * u.kpc) assert isinstance(pic.data, r.PhysicsSphericalRepresentation) - picu = PhysicsICRS(phi=1*u.deg, theta=25*u.deg) + picu = PhysicsICRS(phi=1 * u.deg, theta=25 * u.deg) assert isinstance(picu.data, unitphysics) def test_attribute_repr(): - class Spam: def _astropy_repr_in_frame(self): return "TEST REPR" @@ -1384,11 +1468,13 @@ class NameChangeFrame(BaseCoordinateFrame): frame_specific_representation_info = { r.PhysicsSphericalRepresentation: [ - RepresentationMapping('phi', 'theta', u.deg), - RepresentationMapping('theta', 'phi', u.arcsec), - RepresentationMapping('r', 'JUSTONCE', u.AU)] + RepresentationMapping("phi", "theta", u.deg), + RepresentationMapping("theta", "phi", u.arcsec), + RepresentationMapping("r", "JUSTONCE", u.AU), + ] } - frame = NameChangeFrame(0*u.deg, 0*u.arcsec, 0*u.AU) + + frame = NameChangeFrame(0 * u.deg, 0 * u.arcsec, 0 * u.AU) # Check for the new names in the Frame repr assert "(theta, phi, JUSTONCE)" in repr(frame) @@ -1398,19 +1484,17 @@ class NameChangeFrame(BaseCoordinateFrame): def test_galactocentric_defaults(): - - with galactocentric_frame_defaults.set('pre-v4.0'): + with galactocentric_frame_defaults.set("pre-v4.0"): galcen_pre40 = Galactocentric() - with galactocentric_frame_defaults.set('v4.0'): + with galactocentric_frame_defaults.set("v4.0"): galcen_40 = Galactocentric() - with galactocentric_frame_defaults.set('latest'): + with galactocentric_frame_defaults.set("latest"): galcen_latest = Galactocentric() # parameters that changed - assert not u.allclose(galcen_pre40.galcen_distance, - galcen_40.galcen_distance) + assert not u.allclose(galcen_pre40.galcen_distance, galcen_40.galcen_distance) assert not u.allclose(galcen_pre40.z_sun, galcen_40.z_sun) for k in galcen_40.frame_attributes: @@ -1418,13 +1502,14 @@ def test_galactocentric_defaults(): continue # skip coordinate comparison... elif isinstance(getattr(galcen_40, k), CartesianDifferential): - assert u.allclose(getattr(galcen_40, k).d_xyz, - getattr(galcen_latest, k).d_xyz) + assert u.allclose( + getattr(galcen_40, k).d_xyz, getattr(galcen_latest, k).d_xyz + ) else: assert getattr(galcen_40, k) == getattr(galcen_latest, k) # test validate Galactocentric - with galactocentric_frame_defaults.set('latest'): + with galactocentric_frame_defaults.set("latest"): params = galactocentric_frame_defaults.validate(galcen_latest) references = galcen_latest.frame_attribute_references state = dict(parameters=params, references=references) @@ -1447,43 +1532,42 @@ def test_galactocentric_defaults(): def test_galactocentric_references(): # references in the "scientific paper"-sense - with galactocentric_frame_defaults.set('pre-v4.0'): + with galactocentric_frame_defaults.set("pre-v4.0"): galcen_pre40 = Galactocentric() for k in galcen_pre40.frame_attributes: - if k == 'roll': # no reference for this parameter + if k == "roll": # no reference for this parameter continue assert k in galcen_pre40.frame_attribute_references - with galactocentric_frame_defaults.set('v4.0'): + with galactocentric_frame_defaults.set("v4.0"): galcen_40 = Galactocentric() for k in galcen_40.frame_attributes: - if k == 'roll': # no reference for this parameter + if k == "roll": # no reference for this parameter continue assert k in galcen_40.frame_attribute_references - with galactocentric_frame_defaults.set('v4.0'): - galcen_custom = Galactocentric(z_sun=15*u.pc) + with galactocentric_frame_defaults.set("v4.0"): + galcen_custom = Galactocentric(z_sun=15 * u.pc) for k in galcen_custom.frame_attributes: - if k == 'roll': # no reference for this parameter + if k == "roll": # no reference for this parameter continue - if k == 'z_sun': + if k == "z_sun": assert k not in galcen_custom.frame_attribute_references else: assert k in galcen_custom.frame_attribute_references def test_coordinateattribute_transformation(): - class FrameWithCoordinateAttribute(BaseCoordinateFrame): coord_attr = CoordinateAttribute(HCRS) - hcrs = HCRS(1*u.deg, 2*u.deg, 3*u.AU, obstime='2001-02-03') + hcrs = HCRS(1 * u.deg, 2 * u.deg, 3 * u.AU, obstime="2001-02-03") f1_frame = FrameWithCoordinateAttribute(coord_attr=hcrs) f1_skycoord = FrameWithCoordinateAttribute(coord_attr=SkyCoord(hcrs)) @@ -1492,7 +1576,7 @@ class FrameWithCoordinateAttribute(BaseCoordinateFrame): # The output should not be different if a SkyCoord is provided assert f1_skycoord.coord_attr == f1_frame.coord_attr - gcrs = GCRS(4*u.deg, 5*u.deg, 6*u.AU, obstime='2004-05-06') + gcrs = GCRS(4 * u.deg, 5 * u.deg, 6 * u.AU, obstime="2004-05-06") f2_frame = FrameWithCoordinateAttribute(coord_attr=gcrs) f2_skycoord = FrameWithCoordinateAttribute(coord_attr=SkyCoord(gcrs)) @@ -1505,9 +1589,13 @@ class FrameWithCoordinateAttribute(BaseCoordinateFrame): def test_realize_frame_accepts_kwargs(): - c1 = ICRS(x=1*u.pc, y=2*u.pc, z=3*u.pc, - representation_type=r.CartesianRepresentation) - new_data = r.CartesianRepresentation(x=11*u.pc, y=12*u.pc, z=13*u.pc) + c1 = ICRS( + x=1 * u.pc, + y=2 * u.pc, + z=3 * u.pc, + representation_type=r.CartesianRepresentation, + ) + new_data = r.CartesianRepresentation(x=11 * u.pc, y=12 * u.pc, z=13 * u.pc) c2 = c1.realize_frame(new_data, representation_type="cartesian") c3 = c1.realize_frame(new_data, representation_type="cylindrical") diff --git a/astropy/coordinates/tests/test_frames_with_velocity.py b/astropy/coordinates/tests/test_frames_with_velocity.py index fdb4b95fa4b..a15e0146588 100644 --- a/astropy/coordinates/tests/test_frames_with_velocity.py +++ b/astropy/coordinates/tests/test_frames_with_velocity.py @@ -14,25 +14,40 @@ def test_api(): # transform observed Barycentric velocities to full-space Galactocentric - with galactocentric_frame_defaults.set('latest'): + with galactocentric_frame_defaults.set("latest"): gc_frame = Galactocentric() - icrs = ICRS(ra=151.*u.deg, dec=-16*u.deg, distance=101*u.pc, - pm_ra_cosdec=21*u.mas/u.yr, pm_dec=-71*u.mas/u.yr, - radial_velocity=71*u.km/u.s) + icrs = ICRS( + ra=151.0 * u.deg, + dec=-16 * u.deg, + distance=101 * u.pc, + pm_ra_cosdec=21 * u.mas / u.yr, + pm_dec=-71 * u.mas / u.yr, + radial_velocity=71 * u.km / u.s, + ) icrs.transform_to(gc_frame) # transform a set of ICRS proper motions to Galactic - icrs = ICRS(ra=151.*u.deg, dec=-16*u.deg, - pm_ra_cosdec=21*u.mas/u.yr, pm_dec=-71*u.mas/u.yr) + icrs = ICRS( + ra=151.0 * u.deg, + dec=-16 * u.deg, + pm_ra_cosdec=21 * u.mas / u.yr, + pm_dec=-71 * u.mas / u.yr, + ) icrs.transform_to(Galactic()) # transform a Barycentric RV to a GSR RV - icrs = ICRS(ra=151.*u.deg, dec=-16*u.deg, distance=1.*u.pc, - pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, - radial_velocity=71*u.km/u.s) + icrs = ICRS( + ra=151.0 * u.deg, + dec=-16 * u.deg, + distance=1.0 * u.pc, + pm_ra_cosdec=0 * u.mas / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=71 * u.km / u.s, + ) icrs.transform_to(Galactocentric()) +# fmt: off all_kwargs = [ dict(ra=37.4*u.deg, dec=-55.8*u.deg), dict(ra=37.4*u.deg, dec=-55.8*u.deg, distance=150*u.pc), @@ -64,9 +79,10 @@ def test_api(): representation_type=r.CartesianRepresentation, differential_type='cartesian'), ] +# fmt: on -@pytest.mark.parametrize('kwargs', all_kwargs) +@pytest.mark.parametrize("kwargs", all_kwargs) def test_all_arg_options(kwargs): # Above is a list of all possible valid combinations of arguments. # Here we do a simple thing and just verify that passing them in, we have @@ -76,42 +92,57 @@ def test_all_arg_options(kwargs): repr_gal = repr(gal) for k in kwargs: - if k == 'differential_type': + if k == "differential_type": continue getattr(icrs, k) - if 'pm_ra_cosdec' in kwargs: # should have both - assert 'pm_l_cosb' in repr_gal - assert 'pm_b' in repr_gal - assert 'mas / yr' in repr_gal - - if 'radial_velocity' not in kwargs: - assert 'radial_velocity' not in repr_gal - - if 'radial_velocity' in kwargs: - assert 'radial_velocity' in repr_gal - assert 'km / s' in repr_gal - - if 'pm_ra_cosdec' not in kwargs: - assert 'pm_l_cosb' not in repr_gal - assert 'pm_b' not in repr_gal - - -@pytest.mark.parametrize('cls,lon,lat', [ - [bf.ICRS, 'ra', 'dec'], [bf.FK4, 'ra', 'dec'], [bf.FK4NoETerms, 'ra', 'dec'], - [bf.FK5, 'ra', 'dec'], [bf.GCRS, 'ra', 'dec'], [bf.HCRS, 'ra', 'dec'], - [bf.LSR, 'ra', 'dec'], [bf.CIRS, 'ra', 'dec'], [bf.Galactic, 'l', 'b'], - [bf.AltAz, 'az', 'alt'], [bf.Supergalactic, 'sgl', 'sgb'], - [bf.GalacticLSR, 'l', 'b'], [bf.HeliocentricMeanEcliptic, 'lon', 'lat'], - [bf.GeocentricMeanEcliptic, 'lon', 'lat'], - [bf.BarycentricMeanEcliptic, 'lon', 'lat'], - [bf.PrecessedGeocentric, 'ra', 'dec'] -]) + if "pm_ra_cosdec" in kwargs: # should have both + assert "pm_l_cosb" in repr_gal + assert "pm_b" in repr_gal + assert "mas / yr" in repr_gal + + if "radial_velocity" not in kwargs: + assert "radial_velocity" not in repr_gal + + if "radial_velocity" in kwargs: + assert "radial_velocity" in repr_gal + assert "km / s" in repr_gal + + if "pm_ra_cosdec" not in kwargs: + assert "pm_l_cosb" not in repr_gal + assert "pm_b" not in repr_gal + + +@pytest.mark.parametrize( + "cls,lon,lat", + [ + [bf.ICRS, "ra", "dec"], + [bf.FK4, "ra", "dec"], + [bf.FK4NoETerms, "ra", "dec"], + [bf.FK5, "ra", "dec"], + [bf.GCRS, "ra", "dec"], + [bf.HCRS, "ra", "dec"], + [bf.LSR, "ra", "dec"], + [bf.CIRS, "ra", "dec"], + [bf.Galactic, "l", "b"], + [bf.AltAz, "az", "alt"], + [bf.Supergalactic, "sgl", "sgb"], + [bf.GalacticLSR, "l", "b"], + [bf.HeliocentricMeanEcliptic, "lon", "lat"], + [bf.GeocentricMeanEcliptic, "lon", "lat"], + [bf.BarycentricMeanEcliptic, "lon", "lat"], + [bf.PrecessedGeocentric, "ra", "dec"], + ], +) def test_expected_arg_names(cls, lon, lat): - kwargs = {lon: 37.4*u.deg, lat: -55.8*u.deg, 'distance': 150*u.pc, - f'pm_{lon}_cos{lat}': -21.2*u.mas/u.yr, - f'pm_{lat}': 17.1*u.mas/u.yr, - 'radial_velocity': 105.7*u.km/u.s} + kwargs = { + lon: 37.4 * u.deg, + lat: -55.8 * u.deg, + "distance": 150 * u.pc, + f"pm_{lon}_cos{lat}": -21.2 * u.mas / u.yr, + f"pm_{lat}": 17.1 * u.mas / u.yr, + "radial_velocity": 105.7 * u.km / u.s, + } frame = cls(**kwargs) @@ -122,7 +153,9 @@ def test_expected_arg_names(cls, lon, lat): R D pmRA pmDE Di pmGLon pmGLat RV U V W HIP AJ2000 (deg) EJ2000 (deg) (mas/yr) (mas/yr) GLon (deg) GLat (deg) st (pc) (mas/yr) (mas/yr) (km/s) (km/s) (km/s) (km/s) ------ ------------ ------------ -------- -------- ------------ ------------ ------- -------- -------- ------- ------ ------ ------ -"""[1:-1] +"""[ + 1:-1 +] _xhip_data = """ 19 000.05331690 +38.30408633 -3.17 -15.37 112.00026470 -23.47789171 247.12 -6.40 -14.33 6.30 7.3 2.0 -17.9 20 000.06295067 +23.52928427 36.11 -22.48 108.02779304 -37.85659811 95.90 29.35 -30.78 37.80 -19.3 16.1 -34.2 @@ -131,32 +164,48 @@ def test_expected_arg_names(cls, lon, lat): 59207 182.13915108 +65.34963517 18.17 5.49 130.04157185 51.18258601 56.00 -18.98 -0.49 5.70 1.5 6.1 4.4 87992 269.60730667 +36.87462906 -89.58 72.46 62.98053142 25.90148234 129.60 45.64 105.79 -4.00 -39.5 -15.8 56.7 115110 349.72322473 -28.74087144 48.86 -9.25 23.00447250 -69.52799804 116.87 -8.37 -49.02 15.00 -16.8 -12.2 -23.6 -"""[1:-1] +"""[ + 1:-1 +] # in principal we could parse the above as a table, but doing it "manually" # makes this test less tied to Table working correctly -@pytest.mark.parametrize('hip,ra,dec,pmra,pmdec,glon,glat,dist,pmglon,pmglat,rv,U,V,W', - [[float(val) for val in row.split()] for row in _xhip_data.split('\n')]) -def test_xhip_galactic(hip, ra, dec, pmra, pmdec, glon, glat, dist, pmglon, pmglat, rv, U, V, W): - i = ICRS(ra*u.deg, dec*u.deg, dist*u.pc, - pm_ra_cosdec=pmra*u.marcsec/u.yr, pm_dec=pmdec*u.marcsec/u.yr, - radial_velocity=rv*u.km/u.s) +@pytest.mark.parametrize( + "hip,ra,dec,pmra,pmdec,glon,glat,dist,pmglon,pmglat,rv,U,V,W", + [[float(val) for val in row.split()] for row in _xhip_data.split("\n")], +) +def test_xhip_galactic( + hip, ra, dec, pmra, pmdec, glon, glat, dist, pmglon, pmglat, rv, U, V, W +): + i = ICRS( + ra * u.deg, + dec * u.deg, + dist * u.pc, + pm_ra_cosdec=pmra * u.marcsec / u.yr, + pm_dec=pmdec * u.marcsec / u.yr, + radial_velocity=rv * u.km / u.s, + ) g = i.transform_to(Galactic()) # precision is limited by 2-deciimal digit string representation of pms - assert quantity_allclose(g.pm_l_cosb, pmglon*u.marcsec/u.yr, atol=.01*u.marcsec/u.yr) - assert quantity_allclose(g.pm_b, pmglat*u.marcsec/u.yr, atol=.01*u.marcsec/u.yr) + assert quantity_allclose( + g.pm_l_cosb, pmglon * u.marcsec / u.yr, atol=0.01 * u.marcsec / u.yr + ) + assert quantity_allclose( + g.pm_b, pmglat * u.marcsec / u.yr, atol=0.01 * u.marcsec / u.yr + ) # make sure UVW also makes sense - uvwg = g.cartesian.differentials['s'] + uvwg = g.cartesian.differentials["s"] # precision is limited by 1-decimal digit string representation of vels - assert quantity_allclose(uvwg.d_x, U*u.km/u.s, atol=.1*u.km/u.s) - assert quantity_allclose(uvwg.d_y, V*u.km/u.s, atol=.1*u.km/u.s) - assert quantity_allclose(uvwg.d_z, W*u.km/u.s, atol=.1*u.km/u.s) + assert quantity_allclose(uvwg.d_x, U * u.km / u.s, atol=0.1 * u.km / u.s) + assert quantity_allclose(uvwg.d_y, V * u.km / u.s, atol=0.1 * u.km / u.s) + assert quantity_allclose(uvwg.d_z, W * u.km / u.s, atol=0.1 * u.km / u.s) +# fmt: off @pytest.mark.parametrize('kwargs,expect_success', [ [dict(ra=37.4*u.deg, dec=-55.8*u.deg), False], [dict(ra=37.4*u.deg, dec=-55.8*u.deg, distance=150*u.pc), True], @@ -173,12 +222,13 @@ def test_xhip_galactic(hip, ra, dec, pmra, pmdec, glon, glat, dist, pmglon, pmgl radial_velocity=105.7*u.km/u.s), True] ]) +# fmt: on def test_frame_affinetransform(kwargs, expect_success): """There are already tests in test_transformations.py that check that an AffineTransform fails without full-space data, but this just checks that things work as expected at the frame level as well. """ - with galactocentric_frame_defaults.set('latest'): + with galactocentric_frame_defaults.set("latest"): icrs = ICRS(**kwargs) if expect_success: @@ -196,50 +246,78 @@ def test_differential_type_arg(): """ from astropy.coordinates.builtin_frames import ICRS - icrs = ICRS(ra=1*u.deg, dec=60*u.deg, - pm_ra=10*u.mas/u.yr, pm_dec=-11*u.mas/u.yr, - differential_type=r.UnitSphericalDifferential) - assert icrs.pm_ra == 10*u.mas/u.yr - - icrs = ICRS(ra=1*u.deg, dec=60*u.deg, - pm_ra=10*u.mas/u.yr, pm_dec=-11*u.mas/u.yr, - differential_type={'s': r.UnitSphericalDifferential}) - assert icrs.pm_ra == 10*u.mas/u.yr - - icrs = ICRS(ra=1*u.deg, dec=60*u.deg, - pm_ra_cosdec=10*u.mas/u.yr, pm_dec=-11*u.mas/u.yr) + icrs = ICRS( + ra=1 * u.deg, + dec=60 * u.deg, + pm_ra=10 * u.mas / u.yr, + pm_dec=-11 * u.mas / u.yr, + differential_type=r.UnitSphericalDifferential, + ) + assert icrs.pm_ra == 10 * u.mas / u.yr + + icrs = ICRS( + ra=1 * u.deg, + dec=60 * u.deg, + pm_ra=10 * u.mas / u.yr, + pm_dec=-11 * u.mas / u.yr, + differential_type={"s": r.UnitSphericalDifferential}, + ) + assert icrs.pm_ra == 10 * u.mas / u.yr + + icrs = ICRS( + ra=1 * u.deg, + dec=60 * u.deg, + pm_ra_cosdec=10 * u.mas / u.yr, + pm_dec=-11 * u.mas / u.yr, + ) icrs.set_representation_cls(s=r.UnitSphericalDifferential) - assert quantity_allclose(icrs.pm_ra, 20*u.mas/u.yr) + assert quantity_allclose(icrs.pm_ra, 20 * u.mas / u.yr) # incompatible representation and differential with pytest.raises(TypeError): - ICRS(ra=1*u.deg, dec=60*u.deg, - v_x=1*u.km/u.s, v_y=-2*u.km/u.s, v_z=-2*u.km/u.s, - differential_type=r.CartesianDifferential) + ICRS( + ra=1 * u.deg, + dec=60 * u.deg, + v_x=1 * u.km / u.s, + v_y=-2 * u.km / u.s, + v_z=-2 * u.km / u.s, + differential_type=r.CartesianDifferential, + ) # specify both - icrs = ICRS(x=1*u.pc, y=2*u.pc, z=3*u.pc, - v_x=1*u.km/u.s, v_y=2*u.km/u.s, v_z=3*u.km/u.s, - representation_type=r.CartesianRepresentation, - differential_type=r.CartesianDifferential) - assert icrs.x == 1*u.pc - assert icrs.y == 2*u.pc - assert icrs.z == 3*u.pc - assert icrs.v_x == 1*u.km/u.s - assert icrs.v_y == 2*u.km/u.s - assert icrs.v_z == 3*u.km/u.s + icrs = ICRS( + x=1 * u.pc, + y=2 * u.pc, + z=3 * u.pc, + v_x=1 * u.km / u.s, + v_y=2 * u.km / u.s, + v_z=3 * u.km / u.s, + representation_type=r.CartesianRepresentation, + differential_type=r.CartesianDifferential, + ) + assert icrs.x == 1 * u.pc + assert icrs.y == 2 * u.pc + assert icrs.z == 3 * u.pc + assert icrs.v_x == 1 * u.km / u.s + assert icrs.v_y == 2 * u.km / u.s + assert icrs.v_z == 3 * u.km / u.s def test_slicing_preserves_differential(): - icrs = ICRS(ra=37.4*u.deg, dec=-55.8*u.deg, distance=150*u.pc, - pm_ra_cosdec=-21.2*u.mas/u.yr, pm_dec=17.1*u.mas/u.yr, - radial_velocity=105.7*u.km/u.s) - icrs2 = icrs.reshape(1,1)[:1,0] + icrs = ICRS( + ra=37.4 * u.deg, + dec=-55.8 * u.deg, + distance=150 * u.pc, + pm_ra_cosdec=-21.2 * u.mas / u.yr, + pm_dec=17.1 * u.mas / u.yr, + radial_velocity=105.7 * u.km / u.s, + ) + icrs2 = icrs.reshape(1, 1)[:1, 0] for name in icrs.representation_component_names.keys(): assert getattr(icrs, name) == getattr(icrs2, name)[0] - for name in icrs.get_representation_component_names('s').keys(): + for name in icrs.get_representation_component_names("s").keys(): assert getattr(icrs, name) == getattr(icrs2, name)[0] @@ -248,21 +326,28 @@ def test_shorthand_attributes(): # for array data: n = 4 - icrs1 = ICRS(ra=np.random.uniform(0, 360, n)*u.deg, - dec=np.random.uniform(-90, 90, n)*u.deg, - distance=100*u.pc, - pm_ra_cosdec=np.random.normal(0, 100, n)*u.mas/u.yr, - pm_dec=np.random.normal(0, 100, n)*u.mas/u.yr, - radial_velocity=np.random.normal(0, 100, n)*u.km/u.s) + icrs1 = ICRS( + ra=np.random.uniform(0, 360, n) * u.deg, + dec=np.random.uniform(-90, 90, n) * u.deg, + distance=100 * u.pc, + pm_ra_cosdec=np.random.normal(0, 100, n) * u.mas / u.yr, + pm_dec=np.random.normal(0, 100, n) * u.mas / u.yr, + radial_velocity=np.random.normal(0, 100, n) * u.km / u.s, + ) v = icrs1.velocity pm = icrs1.proper_motion assert quantity_allclose(pm[0], icrs1.pm_ra_cosdec) assert quantity_allclose(pm[1], icrs1.pm_dec) # for scalar data: - icrs2 = ICRS(ra=37.4*u.deg, dec=-55.8*u.deg, distance=150*u.pc, - pm_ra_cosdec=-21.2*u.mas/u.yr, pm_dec=17.1*u.mas/u.yr, - radial_velocity=105.7*u.km/u.s) + icrs2 = ICRS( + ra=37.4 * u.deg, + dec=-55.8 * u.deg, + distance=150 * u.pc, + pm_ra_cosdec=-21.2 * u.mas / u.yr, + pm_dec=17.1 * u.mas / u.yr, + radial_velocity=105.7 * u.km / u.s, + ) v = icrs2.velocity pm = icrs2.proper_motion assert quantity_allclose(pm[0], icrs2.pm_ra_cosdec) @@ -271,40 +356,52 @@ def test_shorthand_attributes(): # check that it fails where we expect: # no distance - rv = 105.7*u.km/u.s - icrs3 = ICRS(ra=37.4*u.deg, dec=-55.8*u.deg, - pm_ra_cosdec=-21.2*u.mas/u.yr, pm_dec=17.1*u.mas/u.yr, - radial_velocity=rv) + rv = 105.7 * u.km / u.s + icrs3 = ICRS( + ra=37.4 * u.deg, + dec=-55.8 * u.deg, + pm_ra_cosdec=-21.2 * u.mas / u.yr, + pm_dec=17.1 * u.mas / u.yr, + radial_velocity=rv, + ) with pytest.raises(ValueError): icrs3.velocity - icrs3.set_representation_cls('cartesian') - assert hasattr(icrs3, 'radial_velocity') + icrs3.set_representation_cls("cartesian") + assert hasattr(icrs3, "radial_velocity") assert quantity_allclose(icrs3.radial_velocity, rv) - icrs4 = ICRS(x=30*u.pc, y=20*u.pc, z=11*u.pc, - v_x=10*u.km/u.s, v_y=10*u.km/u.s, v_z=10*u.km/u.s, - representation_type=r.CartesianRepresentation, - differential_type=r.CartesianDifferential) + icrs4 = ICRS( + x=30 * u.pc, + y=20 * u.pc, + z=11 * u.pc, + v_x=10 * u.km / u.s, + v_y=10 * u.km / u.s, + v_z=10 * u.km / u.s, + representation_type=r.CartesianRepresentation, + differential_type=r.CartesianDifferential, + ) icrs4.radial_velocity def test_negative_distance(): - """ Regression test: #7408 + """Regression test: #7408 Make sure that negative parallaxes turned into distances are handled right """ RA = 150 * u.deg - DEC = -11*u.deg - c = ICRS(ra=RA, dec=DEC, - distance=(-10*u.mas).to(u.pc, u.parallax()), - pm_ra_cosdec=10*u.mas/u.yr, - pm_dec=10*u.mas/u.yr) + DEC = -11 * u.deg + c = ICRS( + ra=RA, + dec=DEC, + distance=(-10 * u.mas).to(u.pc, u.parallax()), + pm_ra_cosdec=10 * u.mas / u.yr, + pm_dec=10 * u.mas / u.yr, + ) assert quantity_allclose(c.ra, RA) assert quantity_allclose(c.dec, DEC) - c = ICRS(ra=RA, dec=DEC, - distance=(-10*u.mas).to(u.pc, u.parallax())) + c = ICRS(ra=RA, dec=DEC, distance=(-10 * u.mas).to(u.pc, u.parallax())) assert quantity_allclose(c.ra, RA) assert quantity_allclose(c.dec, DEC) @@ -312,18 +409,27 @@ def test_negative_distance(): def test_velocity_units(): """Check that the differential data given has compatible units with the time-derivative of representation data""" - msg = ('x has unit "" with physical type "dimensionless", but v_x has ' - 'incompatible unit "" with physical type "dimensionless" instead ' - r'of the expected "frequency"\.') + msg = ( + 'x has unit "" with physical type "dimensionless", but v_x has ' + 'incompatible unit "" with physical type "dimensionless" instead ' + r'of the expected "frequency"\.' + ) with pytest.raises(ValueError, match=msg): c = ICRS( - x=1, y=2, z=3, - v_x=1, v_y=2, v_z=3, + x=1, + y=2, + z=3, + v_x=1, + v_y=2, + v_z=3, representation_type=r.CartesianRepresentation, - differential_type=r.CartesianDifferential) + differential_type=r.CartesianDifferential, + ) def test_frame_with_velocity_without_distance_can_be_transformed(): - frame = CIRS(1*u.deg, 2*u.deg, pm_dec=1*u.mas/u.yr, pm_ra_cosdec=2*u.mas/u.yr) + frame = CIRS( + 1 * u.deg, 2 * u.deg, pm_dec=1 * u.mas / u.yr, pm_ra_cosdec=2 * u.mas / u.yr + ) rep = frame.transform_to(ICRS()) assert " AltAz """ # create the altaz frame - altazframe = AltAz(obstime=fullstack_times, location=fullstack_locations, - pressure=fullstack_obsconditions[0], - temperature=fullstack_obsconditions[1], - relative_humidity=fullstack_obsconditions[2], - obswl=fullstack_obsconditions[3]) + altazframe = AltAz( + obstime=fullstack_times, + location=fullstack_locations, + pressure=fullstack_obsconditions[0], + temperature=fullstack_obsconditions[1], + relative_humidity=fullstack_obsconditions[2], + obswl=fullstack_obsconditions[3], + ) aacoo = fullstack_icrs.transform_to(altazframe) # compare aacoo to the fiducial AltAz - should always be different - assert np.all(np.abs(aacoo.alt - fullstack_fiducial_altaz.alt) > 50*u.milliarcsecond) - assert np.all(np.abs(aacoo.az - fullstack_fiducial_altaz.az) > 50*u.milliarcsecond) + assert np.all( + np.abs(aacoo.alt - fullstack_fiducial_altaz.alt) > 50 * u.milliarcsecond + ) + assert np.all( + np.abs(aacoo.az - fullstack_fiducial_altaz.az) > 50 * u.milliarcsecond + ) # if the refraction correction is included, we *only* do the comparisons # where altitude >5 degrees. The SOFA guides imply that below 5 is where @@ -97,20 +117,24 @@ def test_iau_fullstack(fullstack_icrs, fullstack_fiducial_altaz, if fullstack_obsconditions[0].value == 0: # but if there is no refraction correction, check everything msk = slice(None) - tol = 5*u.microarcsecond + tol = 5 * u.microarcsecond else: - msk = aacoo.alt > 5*u.deg + msk = aacoo.alt > 5 * u.deg # most of them aren't this bad, but some of those at low alt are offset # this much. For alt > 10, this is always better than 100 masec - tol = 750*u.milliarcsecond + tol = 750 * u.milliarcsecond # now make sure the full stack round-tripping works icrs2 = aacoo.transform_to(ICRS()) adras = np.abs(fullstack_icrs.ra - icrs2.ra)[msk] addecs = np.abs(fullstack_icrs.dec - icrs2.dec)[msk] - assert np.all(adras < tol), f'largest RA change is {np.max(adras.arcsec * 1000)} mas, > {tol}' - assert np.all(addecs < tol), f'largest Dec change is {np.max(addecs.arcsec * 1000)} mas, > {tol}' + assert np.all( + adras < tol + ), f"largest RA change is {np.max(adras.arcsec * 1000)} mas, > {tol}" + assert np.all( + addecs < tol + ), f"largest Dec change is {np.max(addecs.arcsec * 1000)} mas, > {tol}" # check that we're consistent with the ERFA alt/az result iers_tab = iers.earth_orientation_table.get() @@ -118,21 +142,29 @@ def test_iau_fullstack(fullstack_icrs, fullstack_fiducial_altaz, lon = fullstack_locations.geodetic[0].to_value(u.radian) lat = fullstack_locations.geodetic[1].to_value(u.radian) height = fullstack_locations.geodetic[2].to_value(u.m) - jd1, jd2 = get_jd12(fullstack_times, 'utc') + jd1, jd2 = get_jd12(fullstack_times, "utc") pressure = fullstack_obsconditions[0].to_value(u.hPa) temperature = fullstack_obsconditions[1].to_value(u.deg_C) # Relative humidity can be a quantity or a number. relative_humidity = u.Quantity(fullstack_obsconditions[2], u.one).value obswl = fullstack_obsconditions[3].to_value(u.micron) - astrom, eo = erfa.apco13(jd1, jd2, - fullstack_times.delta_ut1_utc, - lon, lat, height, - xp, yp, - pressure, temperature, relative_humidity, - obswl) + astrom, eo = erfa.apco13( + jd1, + jd2, + fullstack_times.delta_ut1_utc, + lon, + lat, + height, + xp, + yp, + pressure, + temperature, + relative_humidity, + obswl, + ) erfadct = _erfa_check(fullstack_icrs.ra.rad, fullstack_icrs.dec.rad, astrom) - npt.assert_allclose(erfadct['alt'], aacoo.alt.radian, atol=1e-7) - npt.assert_allclose(erfadct['az'], aacoo.az.radian, atol=1e-7) + npt.assert_allclose(erfadct["alt"], aacoo.alt.radian, atol=1e-7) + npt.assert_allclose(erfadct["az"], aacoo.az.radian, atol=1e-7) def test_fiducial_roudtrip(fullstack_icrs, fullstack_fiducial_altaz): @@ -157,22 +189,26 @@ def test_future_altaz(): # appeared from astropy.coordinates.builtin_frames import utils from astropy.utils.exceptions import AstropyWarning - if hasattr(utils, '__warningregistry__'): + + if hasattr(utils, "__warningregistry__"): utils.__warningregistry__.clear() - location = EarthLocation(lat=0*u.deg, lon=0*u.deg) - t = Time('J2161') + location = EarthLocation(lat=0 * u.deg, lon=0 * u.deg) + t = Time("J2161") # check that these message(s) appear among any other warnings. If tests are run with # --remote-data then the IERS table will be an instance of IERS_Auto which is # assured of being "fresh". In this case getting times outside the range of the # table does not raise an exception. Only if using IERS_B (which happens without # --remote-data, i.e. for all CI testing) do we expect another warning. - with pytest.warns(AstropyWarning, match=r"Tried to get polar motions for " - "times after IERS data is valid.*") as found_warnings: - SkyCoord(1*u.deg, 2*u.deg).transform_to(AltAz(location=location, obstime=t)) + with pytest.warns( + AstropyWarning, + match=r"Tried to get polar motions for " "times after IERS data is valid.*", + ) as found_warnings: + SkyCoord(1 * u.deg, 2 * u.deg).transform_to(AltAz(location=location, obstime=t)) if isinstance(iers.earth_orientation_table.get(), iers.IERS_B): - messages_found = ["(some) times are outside of range covered by IERS " - "table." in str(w.message) for w in found_warnings] - assert any(messages_found) + assert any( + "(some) times are outside of range covered by IERS table." in str(w.message) + for w in found_warnings + ) diff --git a/astropy/coordinates/tests/test_icrs_observed_transformations.py b/astropy/coordinates/tests/test_icrs_observed_transformations.py index 9179c0d51c7..c7b60ccdc14 100644 --- a/astropy/coordinates/tests/test_icrs_observed_transformations.py +++ b/astropy/coordinates/tests/test_icrs_observed_transformations.py @@ -29,8 +29,8 @@ def test_icrs_altaz_consistency(): dist = np.linspace(0.5, 1, len(usph)) * u.km * 1e5 icoo = SkyCoord(ra=usph.lon, dec=usph.lat, distance=dist) - observer = EarthLocation(28*u.deg, 23*u.deg, height=2000.*u.km) - obstime = Time('J2010') + observer = EarthLocation(28 * u.deg, 23 * u.deg, height=2000.0 * u.km) + obstime = Time("J2010") aa_frame = AltAz(obstime=obstime, location=observer) # check we are going direct! @@ -40,16 +40,15 @@ def test_icrs_altaz_consistency(): # check that ICRS-AltAz and ICRS->CIRS->AltAz are consistent aa1 = icoo.transform_to(aa_frame) aa2 = icoo.transform_to(CIRS()).transform_to(aa_frame) - assert_allclose(aa1.separation_3d(aa2), 0*u.mm, atol=1*u.mm) + assert_allclose(aa1.separation_3d(aa2), 0 * u.mm, atol=1 * u.mm) # check roundtrip roundtrip = icoo.transform_to(aa_frame).transform_to(icoo) - assert_allclose(roundtrip.separation_3d(icoo), 0*u.mm, atol=1*u.mm) + assert_allclose(roundtrip.separation_3d(icoo), 0 * u.mm, atol=1 * u.mm) # check there and back via CIRS mish-mash - roundtrip = icoo.transform_to(aa_frame).transform_to( - CIRS()).transform_to(icoo) - assert_allclose(roundtrip.separation_3d(icoo), 0*u.mm, atol=1*u.mm) + roundtrip = icoo.transform_to(aa_frame).transform_to(CIRS()).transform_to(icoo) + assert_allclose(roundtrip.separation_3d(icoo), 0 * u.mm, atol=1 * u.mm) def test_icrs_hadec_consistency(): @@ -60,8 +59,8 @@ def test_icrs_hadec_consistency(): dist = np.linspace(0.5, 1, len(usph)) * u.km * 1e5 icoo = SkyCoord(ra=usph.lon, dec=usph.lat, distance=dist) - observer = EarthLocation(28*u.deg, 23*u.deg, height=2000.*u.km) - obstime = Time('J2010') + observer = EarthLocation(28 * u.deg, 23 * u.deg, height=2000.0 * u.km) + obstime = Time("J2010") hd_frame = HADec(obstime=obstime, location=observer) # check we are going direct! @@ -71,13 +70,12 @@ def test_icrs_hadec_consistency(): # check that ICRS-HADec and ICRS->CIRS->HADec are consistent aa1 = icoo.transform_to(hd_frame) aa2 = icoo.transform_to(CIRS()).transform_to(hd_frame) - assert_allclose(aa1.separation_3d(aa2), 0*u.mm, atol=1*u.mm) + assert_allclose(aa1.separation_3d(aa2), 0 * u.mm, atol=1 * u.mm) # check roundtrip roundtrip = icoo.transform_to(hd_frame).transform_to(icoo) - assert_allclose(roundtrip.separation_3d(icoo), 0*u.mm, atol=1*u.mm) + assert_allclose(roundtrip.separation_3d(icoo), 0 * u.mm, atol=1 * u.mm) # check there and back via CIRS mish-mash - roundtrip = icoo.transform_to(hd_frame).transform_to( - CIRS()).transform_to(icoo) - assert_allclose(roundtrip.separation_3d(icoo), 0*u.mm, atol=1*u.mm) + roundtrip = icoo.transform_to(hd_frame).transform_to(CIRS()).transform_to(icoo) + assert_allclose(roundtrip.separation_3d(icoo), 0 * u.mm, atol=1 * u.mm) diff --git a/astropy/coordinates/tests/test_intermediate_transformations.py b/astropy/coordinates/tests/test_intermediate_transformations.py index 13738e93150..7bfeb74dbff 100644 --- a/astropy/coordinates/tests/test_intermediate_transformations.py +++ b/astropy/coordinates/tests/test_intermediate_transformations.py @@ -51,7 +51,7 @@ from astropy.utils.compat.optional_deps import HAS_JPLEPHEM from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning -CI = os.environ.get('CI', False) == "true" +CI = os.environ.get("CI", False) == "true" def test_icrs_cirs(): @@ -62,7 +62,7 @@ def test_icrs_cirs(): through ICRS """ usph = golden_spiral_grid(200) - dist = np.linspace(0., 1, len(usph)) * u.pc + dist = np.linspace(0.0, 1, len(usph)) * u.pc inod = ICRS(usph) iwd = ICRS(ra=usph.lon, dec=usph.lat, distance=dist) @@ -74,7 +74,7 @@ def test_icrs_cirs(): assert_allclose(inod.dec, inod2.dec) # now check that a different time yields different answers - cframe2 = CIRS(obstime=Time('J2005')) + cframe2 = CIRS(obstime=Time("J2005")) cirsnod2 = inod.transform_to(cframe2) assert not allclose(cirsnod.ra, cirsnod2.ra, rtol=1e-8) assert not allclose(cirsnod.dec, cirsnod2.dec, rtol=1e-8) @@ -103,10 +103,10 @@ def test_icrs_cirs(): usph = golden_spiral_grid(200) dist = np.linspace(0.5, 1, len(usph)) * u.pc icrs_coords = [ICRS(usph), ICRS(usph.lon, usph.lat, distance=dist)] -gcrs_frames = [GCRS(), GCRS(obstime=Time('J2005'))] +gcrs_frames = [GCRS(), GCRS(obstime=Time("J2005"))] -@pytest.mark.parametrize('icoo', icrs_coords) +@pytest.mark.parametrize("icoo", icrs_coords) def test_icrs_gcrs(icoo): """ Check ICRS<->GCRS for consistency @@ -121,8 +121,8 @@ def test_icrs_gcrs(icoo): # now check that a different time yields different answers gcrscoo2 = icoo.transform_to(gcrs_frames[1]) - assert not allclose(gcrscoo.ra, gcrscoo2.ra, rtol=1e-8, atol=1e-10*u.deg) - assert not allclose(gcrscoo.dec, gcrscoo2.dec, rtol=1e-8, atol=1e-10*u.deg) + assert not allclose(gcrscoo.ra, gcrscoo2.ra, rtol=1e-8, atol=1e-10 * u.deg) + assert not allclose(gcrscoo.dec, gcrscoo2.dec, rtol=1e-8, atol=1e-10 * u.deg) # now check that the cirs self-transform works as expected gcrscoo3 = gcrscoo.transform_to(gcrs_frames[0]) # should be a no-op @@ -130,25 +130,25 @@ def test_icrs_gcrs(icoo): assert_allclose(gcrscoo.dec, gcrscoo3.dec) gcrscoo4 = gcrscoo.transform_to(gcrs_frames[1]) # should be different - assert not allclose(gcrscoo4.ra, gcrscoo.ra, rtol=1e-8, atol=1e-10*u.deg) - assert not allclose(gcrscoo4.dec, gcrscoo.dec, rtol=1e-8, atol=1e-10*u.deg) + assert not allclose(gcrscoo4.ra, gcrscoo.ra, rtol=1e-8, atol=1e-10 * u.deg) + assert not allclose(gcrscoo4.dec, gcrscoo.dec, rtol=1e-8, atol=1e-10 * u.deg) gcrscoo5 = gcrscoo4.transform_to(gcrs_frames[0]) # should be back to the same - assert_allclose(gcrscoo.ra, gcrscoo5.ra, rtol=1e-8, atol=1e-10*u.deg) - assert_allclose(gcrscoo.dec, gcrscoo5.dec, rtol=1e-8, atol=1e-10*u.deg) + assert_allclose(gcrscoo.ra, gcrscoo5.ra, rtol=1e-8, atol=1e-10 * u.deg) + assert_allclose(gcrscoo.dec, gcrscoo5.dec, rtol=1e-8, atol=1e-10 * u.deg) # also make sure that a GCRS with a different geoloc/geovel gets a different answer # roughly a moon-like frame - gframe3 = GCRS(obsgeoloc=[385000., 0, 0]*u.km, obsgeovel=[1, 0, 0]*u.km/u.s) + gframe3 = GCRS(obsgeoloc=[385000.0, 0, 0] * u.km, obsgeovel=[1, 0, 0] * u.km / u.s) gcrscoo6 = icoo.transform_to(gframe3) # should be different - assert not allclose(gcrscoo.ra, gcrscoo6.ra, rtol=1e-8, atol=1e-10*u.deg) - assert not allclose(gcrscoo.dec, gcrscoo6.dec, rtol=1e-8, atol=1e-10*u.deg) + assert not allclose(gcrscoo.ra, gcrscoo6.ra, rtol=1e-8, atol=1e-10 * u.deg) + assert not allclose(gcrscoo.dec, gcrscoo6.dec, rtol=1e-8, atol=1e-10 * u.deg) icooviag3 = gcrscoo6.transform_to(ICRS()) # and now back to the original assert_allclose(icoo.ra, icooviag3.ra) assert_allclose(icoo.dec, icooviag3.dec) -@pytest.mark.parametrize('gframe', gcrs_frames) +@pytest.mark.parametrize("gframe", gcrs_frames) def test_icrs_gcrs_dist_diff(gframe): """ Check that with and without distance give different ICRS<->GCRS answers @@ -157,11 +157,12 @@ def test_icrs_gcrs_dist_diff(gframe): gcrswd = icrs_coords[1].transform_to(gframe) # parallax effects should be included, so with and w/o distance should be different - assert not allclose(gcrswd.ra, gcrsnod.ra, rtol=1e-8, atol=1e-10*u.deg) - assert not allclose(gcrswd.dec, gcrsnod.dec, rtol=1e-8, atol=1e-10*u.deg) + assert not allclose(gcrswd.ra, gcrsnod.ra, rtol=1e-8, atol=1e-10 * u.deg) + assert not allclose(gcrswd.dec, gcrsnod.dec, rtol=1e-8, atol=1e-10 * u.deg) # and the distance should transform at least somehow - assert not allclose(gcrswd.distance, icrs_coords[1].distance, rtol=1e-8, - atol=1e-10*u.pc) + assert not allclose( + gcrswd.distance, icrs_coords[1].distance, rtol=1e-8, atol=1e-10 * u.pc + ) def test_cirs_to_altaz(): @@ -173,12 +174,14 @@ def test_cirs_to_altaz(): usph = golden_spiral_grid(200) dist = np.linspace(0.5, 1, len(usph)) * u.pc - cirs = CIRS(usph, obstime='J2000') + cirs = CIRS(usph, obstime="J2000") crepr = SphericalRepresentation(lon=usph.lon, lat=usph.lat, distance=dist) - cirscart = CIRS(crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation) + cirscart = CIRS( + crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation + ) - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg, height=0*u.m) - altazframe = AltAz(location=loc, obstime=Time('J2005')) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) + altazframe = AltAz(location=loc, obstime=Time("J2005")) cirs2 = cirs.transform_to(altazframe).transform_to(cirs) cirs3 = cirscart.transform_to(altazframe).transform_to(cirs) @@ -198,12 +201,14 @@ def test_cirs_to_hadec(): usph = golden_spiral_grid(200) dist = np.linspace(0.5, 1, len(usph)) * u.pc - cirs = CIRS(usph, obstime='J2000') + cirs = CIRS(usph, obstime="J2000") crepr = SphericalRepresentation(lon=usph.lon, lat=usph.lat, distance=dist) - cirscart = CIRS(crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation) + cirscart = CIRS( + crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation + ) - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg, height=0*u.m) - hadecframe = HADec(location=loc, obstime=Time('J2005')) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) + hadecframe = HADec(location=loc, obstime=Time("J2005")) cirs2 = cirs.transform_to(hadecframe).transform_to(cirs) cirs3 = cirscart.transform_to(hadecframe).transform_to(cirs) @@ -216,15 +221,15 @@ def test_cirs_to_hadec(): def test_itrs_topo_to_altaz_with_refraction(): - - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg, height=0*u.m) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) usph = golden_spiral_grid(200) - dist = np.linspace(1., 1000.0, len(usph)) * u.au + dist = np.linspace(1.0, 1000.0, len(usph)) * u.au icrs = ICRS(ra=usph.lon, dec=usph.lat, distance=dist) - altaz_frame1 = AltAz(obstime = 'J2000', location=loc) - altaz_frame2 = AltAz(obstime = 'J2000', location=loc, pressure=1000.0 * u.hPa, - relative_humidity=0.5) - cirs_frame = CIRS(obstime = 'J2000', location=loc) + altaz_frame1 = AltAz(obstime="J2000", location=loc) + altaz_frame2 = AltAz( + obstime="J2000", location=loc, pressure=1000.0 * u.hPa, relative_humidity=0.5 + ) + cirs_frame = CIRS(obstime="J2000", location=loc) itrs_frame = ITRS(location=loc) # Normal route @@ -243,9 +248,9 @@ def test_itrs_topo_to_altaz_with_refraction(): itrs = icrs.transform_to(itrs_frame) altaz11 = itrs.transform_to(altaz_frame1) - assert_allclose(altaz11.az - altaz1.az, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz11.alt - altaz1.alt, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz11.distance - altaz1.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(altaz11.az - altaz1.az, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz11.alt - altaz1.alt, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz11.distance - altaz1.distance, 0 * u.cm, atol=10.0 * u.cm) # Round trip itrs11 = altaz11.transform_to(itrs_frame) @@ -257,29 +262,29 @@ def test_itrs_topo_to_altaz_with_refraction(): # Refraction added altaz22 = itrs.transform_to(altaz_frame2) - assert_allclose(altaz22.az - altaz2.az, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz22.alt - altaz2.alt, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz22.distance - altaz2.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(altaz22.az - altaz2.az, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz22.alt - altaz2.alt, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz22.distance - altaz2.distance, 0 * u.cm, atol=10.0 * u.cm) # Refraction removed itrs = altaz22.transform_to(itrs_frame) altaz33 = itrs.transform_to(altaz_frame1) - assert_allclose(altaz33.az - altaz3.az, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz33.alt - altaz3.alt, 0*u.mas, atol=0.1*u.mas) - assert_allclose(altaz33.distance - altaz3.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(altaz33.az - altaz3.az, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz33.alt - altaz3.alt, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(altaz33.distance - altaz3.distance, 0 * u.cm, atol=10.0 * u.cm) def test_itrs_topo_to_hadec_with_refraction(): - - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg, height=0*u.m) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) usph = golden_spiral_grid(200) - dist = np.linspace(1., 1000.0, len(usph)) * u.au + dist = np.linspace(1.0, 1000.0, len(usph)) * u.au icrs = ICRS(ra=usph.lon, dec=usph.lat, distance=dist) - hadec_frame1 = HADec(obstime = 'J2000', location=loc) - hadec_frame2 = HADec(obstime = 'J2000', location=loc, pressure=1000.0 * u.hPa, - relative_humidity=0.5) - cirs_frame = CIRS(obstime = 'J2000', location=loc) + hadec_frame1 = HADec(obstime="J2000", location=loc) + hadec_frame2 = HADec( + obstime="J2000", location=loc, pressure=1000.0 * u.hPa, relative_humidity=0.5 + ) + cirs_frame = CIRS(obstime="J2000", location=loc) itrs_frame = ITRS(location=loc) # Normal route @@ -298,9 +303,9 @@ def test_itrs_topo_to_hadec_with_refraction(): itrs = icrs.transform_to(itrs_frame) hadec11 = itrs.transform_to(hadec_frame1) - assert_allclose(hadec11.ha - hadec1.ha, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec11.dec - hadec1.dec, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec11.distance - hadec1.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(hadec11.ha - hadec1.ha, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec11.dec - hadec1.dec, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec11.distance - hadec1.distance, 0 * u.cm, atol=10.0 * u.cm) # Round trip itrs11 = hadec11.transform_to(itrs_frame) @@ -312,17 +317,17 @@ def test_itrs_topo_to_hadec_with_refraction(): # Refraction added hadec22 = itrs.transform_to(hadec_frame2) - assert_allclose(hadec22.ha - hadec2.ha, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec22.dec - hadec2.dec, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec22.distance - hadec2.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(hadec22.ha - hadec2.ha, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec22.dec - hadec2.dec, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec22.distance - hadec2.distance, 0 * u.cm, atol=10.0 * u.cm) # Refraction removed itrs = hadec22.transform_to(itrs_frame) hadec33 = itrs.transform_to(hadec_frame1) - assert_allclose(hadec33.ha - hadec3.ha, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec33.dec - hadec3.dec, 0*u.mas, atol=0.1*u.mas) - assert_allclose(hadec33.distance - hadec3.distance, 0*u.cm, atol=10.0*u.cm) + assert_allclose(hadec33.ha - hadec3.ha, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec33.dec - hadec3.dec, 0 * u.mas, atol=0.1 * u.mas) + assert_allclose(hadec33.distance - hadec3.distance, 0 * u.cm, atol=10.0 * u.cm) def test_gcrs_itrs(): @@ -330,8 +335,8 @@ def test_gcrs_itrs(): Check basic GCRS<->ITRS transforms for round-tripping. """ usph = golden_spiral_grid(200) - gcrs = GCRS(usph, obstime='J2000') - gcrs6 = GCRS(usph, obstime='J2006') + gcrs = GCRS(usph, obstime="J2000") + gcrs6 = GCRS(usph, obstime="J2006") gcrs2 = gcrs.transform_to(ITRS()).transform_to(gcrs) gcrs6_2 = gcrs6.transform_to(ITRS()).transform_to(gcrs) @@ -355,8 +360,8 @@ def test_cirs_itrs(): Check basic CIRS<->ITRS geocentric transforms for round-tripping. """ usph = golden_spiral_grid(200) - cirs = CIRS(usph, obstime='J2000') - cirs6 = CIRS(usph, obstime='J2006') + cirs = CIRS(usph, obstime="J2000") + cirs6 = CIRS(usph, obstime="J2006") cirs2 = cirs.transform_to(ITRS()).transform_to(cirs) cirs6_2 = cirs6.transform_to(ITRS()).transform_to(cirs) # different obstime @@ -372,13 +377,14 @@ def test_cirs_itrs_topo(): """ Check basic CIRS<->ITRS topocentric transforms for round-tripping. """ - loc = EarthLocation(lat=0*u.deg, lon=0*u.deg, height=0*u.m) + loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) usph = golden_spiral_grid(200) - cirs = CIRS(usph, obstime='J2000', location=loc) - cirs6 = CIRS(usph, obstime='J2006', location=loc) + cirs = CIRS(usph, obstime="J2000", location=loc) + cirs6 = CIRS(usph, obstime="J2006", location=loc) cirs2 = cirs.transform_to(ITRS(location=loc)).transform_to(cirs) - cirs6_2 = cirs6.transform_to(ITRS(location=loc)).transform_to(cirs) # different obstime + # different obstime + cirs6_2 = cirs6.transform_to(ITRS(location=loc)).transform_to(cirs) # just check round-tripping assert_allclose(cirs.ra, cirs2.ra) @@ -393,8 +399,8 @@ def test_gcrs_cirs(): above two because it's multi-hop """ usph = golden_spiral_grid(200) - gcrs = GCRS(usph, obstime='J2000') - gcrs6 = GCRS(usph, obstime='J2006') + gcrs = GCRS(usph, obstime="J2000") + gcrs6 = GCRS(usph, obstime="J2006") gcrs2 = gcrs.transform_to(CIRS()).transform_to(gcrs) gcrs6_2 = gcrs6.transform_to(CIRS()).transform_to(gcrs) @@ -406,11 +412,21 @@ def test_gcrs_cirs(): assert not allclose(gcrs.dec, gcrs6_2.dec, rtol=1e-8) # now try explicit intermediate pathways and ensure they're all consistent - gcrs3 = gcrs.transform_to(ITRS()).transform_to(CIRS()).transform_to(ITRS()).transform_to(gcrs) + gcrs3 = ( + gcrs.transform_to(ITRS()) + .transform_to(CIRS()) + .transform_to(ITRS()) + .transform_to(gcrs) + ) assert_allclose(gcrs.ra, gcrs3.ra) assert_allclose(gcrs.dec, gcrs3.dec) - gcrs4 = gcrs.transform_to(ICRS()).transform_to(CIRS()).transform_to(ICRS()).transform_to(gcrs) + gcrs4 = ( + gcrs.transform_to(ICRS()) + .transform_to(CIRS()) + .transform_to(ICRS()) + .transform_to(gcrs) + ) assert_allclose(gcrs.ra, gcrs4.ra) assert_allclose(gcrs.dec, gcrs4.dec) @@ -422,13 +438,12 @@ def test_gcrs_altaz(): from astropy.coordinates import EarthLocation usph = golden_spiral_grid(128) - gcrs = GCRS(usph, obstime='J2000')[None] # broadcast with times below + gcrs = GCRS(usph, obstime="J2000")[None] # broadcast with times below # check array times sure N-d arrays work - times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, - format='jd')[:, None] + times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, format="jd")[:, None] - loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg) + loc = EarthLocation(lon=10 * u.deg, lat=80.0 * u.deg) aaframe = AltAz(obstime=times, location=loc) aa1 = gcrs.transform_to(aaframe) @@ -449,13 +464,12 @@ def test_gcrs_hadec(): from astropy.coordinates import EarthLocation usph = golden_spiral_grid(128) - gcrs = GCRS(usph, obstime='J2000') # broadcast with times below + gcrs = GCRS(usph, obstime="J2000") # broadcast with times below # check array times sure N-d arrays work - times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, - format='jd')[:, np.newaxis] + times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, format="jd")[:, None] - loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg) + loc = EarthLocation(lon=10 * u.deg, lat=80.0 * u.deg) hdframe = HADec(obstime=times, location=loc) hd1 = gcrs.transform_to(hdframe) @@ -470,12 +484,12 @@ def test_gcrs_hadec(): def test_precessed_geocentric(): - assert PrecessedGeocentric().equinox.jd == Time('J2000').jd + assert PrecessedGeocentric().equinox.jd == Time("J2000").jd - gcrs_coo = GCRS(180*u.deg, 2*u.deg, distance=10000*u.km) + gcrs_coo = GCRS(180 * u.deg, 2 * u.deg, distance=10000 * u.km) pgeo_coo = gcrs_coo.transform_to(PrecessedGeocentric()) - assert np.abs(gcrs_coo.ra - pgeo_coo.ra) > 10*u.marcsec - assert np.abs(gcrs_coo.dec - pgeo_coo.dec) > 10*u.marcsec + assert np.abs(gcrs_coo.ra - pgeo_coo.ra) > 10 * u.marcsec + assert np.abs(gcrs_coo.dec - pgeo_coo.dec) > 10 * u.marcsec assert_allclose(gcrs_coo.distance, pgeo_coo.distance) gcrs_roundtrip = pgeo_coo.transform_to(GCRS()) @@ -483,9 +497,9 @@ def test_precessed_geocentric(): assert_allclose(gcrs_coo.dec, gcrs_roundtrip.dec) assert_allclose(gcrs_coo.distance, gcrs_roundtrip.distance) - pgeo_coo2 = gcrs_coo.transform_to(PrecessedGeocentric(equinox='B1850')) - assert np.abs(gcrs_coo.ra - pgeo_coo2.ra) > 1.5*u.deg - assert np.abs(gcrs_coo.dec - pgeo_coo2.dec) > 0.5*u.deg + pgeo_coo2 = gcrs_coo.transform_to(PrecessedGeocentric(equinox="B1850")) + assert np.abs(gcrs_coo.ra - pgeo_coo2.ra) > 1.5 * u.deg + assert np.abs(gcrs_coo.dec - pgeo_coo2.dec) > 0.5 * u.deg assert_allclose(gcrs_coo.distance, pgeo_coo2.distance) gcrs2_roundtrip = pgeo_coo2.transform_to(GCRS()) @@ -496,11 +510,11 @@ def test_precessed_geocentric(): def test_precessed_geocentric_different_obstime(): # Create two PrecessedGeocentric frames with different obstime - precessedgeo1 = PrecessedGeocentric(obstime='2021-09-07') - precessedgeo2 = PrecessedGeocentric(obstime='2021-06-07') + precessedgeo1 = PrecessedGeocentric(obstime="2021-09-07") + precessedgeo2 = PrecessedGeocentric(obstime="2021-06-07") # GCRS->PrecessedGeocentric should give different results for the two frames - gcrs_coord = GCRS(10*u.deg, 20*u.deg, 3*u.AU, obstime=precessedgeo1.obstime) + gcrs_coord = GCRS(10 * u.deg, 20 * u.deg, 3 * u.AU, obstime=precessedgeo1.obstime) pg_coord1 = gcrs_coord.transform_to(precessedgeo1) pg_coord2 = gcrs_coord.transform_to(precessedgeo2) assert not pg_coord1.is_equivalent_frame(pg_coord2) @@ -516,39 +530,48 @@ def test_precessed_geocentric_different_obstime(): # shared by parametrized tests below. Some use the whole AltAz, others use just obstime -totest_frames = [AltAz(location=EarthLocation(-90*u.deg, 65*u.deg), - obstime=Time('J2000')), # J2000 is often a default so this might work when others don't - AltAz(location=EarthLocation(120*u.deg, -35*u.deg), - obstime=Time('J2000')), - AltAz(location=EarthLocation(-90*u.deg, 65*u.deg), - obstime=Time('2014-01-01 00:00:00')), - AltAz(location=EarthLocation(-90*u.deg, 65*u.deg), - obstime=Time('2014-08-01 08:00:00')), - AltAz(location=EarthLocation(120*u.deg, -35*u.deg), - obstime=Time('2014-01-01 00:00:00')) - ] -MOONDIST = 385000*u.km # approximate moon semi-major orbit axis of moon -MOONDIST_CART = CartesianRepresentation(3**-0.5*MOONDIST, 3**-0.5*MOONDIST, 3**-0.5*MOONDIST) -EARTHECC = 0.017 + 0.005 # roughly earth orbital eccentricity, but with an added tolerance - - -@pytest.mark.parametrize('testframe', totest_frames) +totest_frames = [ + # J2000 is often a default so this might work when others don't + AltAz(location=EarthLocation(-90 * u.deg, 65 * u.deg), obstime=Time("J2000")), + AltAz(location=EarthLocation(120 * u.deg, -35 * u.deg), obstime=Time("J2000")), + AltAz( + location=EarthLocation(-90 * u.deg, 65 * u.deg), + obstime=Time("2014-01-01 00:00:00"), + ), + AltAz( + location=EarthLocation(-90 * u.deg, 65 * u.deg), + obstime=Time("2014-08-01 08:00:00"), + ), + AltAz( + location=EarthLocation(120 * u.deg, -35 * u.deg), + obstime=Time("2014-01-01 00:00:00"), + ), +] +MOONDIST = 385000 * u.km # approximate moon semi-major orbit axis of moon +MOONDIST_CART = CartesianRepresentation( + 3**-0.5 * MOONDIST, 3**-0.5 * MOONDIST, 3**-0.5 * MOONDIST +) +# roughly earth orbital eccentricity, but with an added tolerance +EARTHECC = 0.017 + 0.005 + + +@pytest.mark.parametrize("testframe", totest_frames) def test_gcrs_altaz_sunish(testframe): """ Sanity-check that the sun is at a reasonable distance from any altaz """ sun = get_sun(testframe.obstime) - assert sun.frame.name == 'gcrs' + assert sun.frame.name == "gcrs" # the .to(u.au) is not necessary, it just makes the asserts on failure more readable - assert (EARTHECC - 1)*u.au < sun.distance.to(u.au) < (EARTHECC + 1)*u.au + assert (EARTHECC - 1) * u.au < sun.distance.to(u.au) < (EARTHECC + 1) * u.au sunaa = sun.transform_to(testframe) - assert (EARTHECC - 1)*u.au < sunaa.distance.to(u.au) < (EARTHECC + 1)*u.au + assert (EARTHECC - 1) * u.au < sunaa.distance.to(u.au) < (EARTHECC + 1) * u.au -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_gcrs_altaz_moonish(testframe): """ Sanity-check that an object resembling the moon goes to the right place with @@ -559,7 +582,7 @@ def test_gcrs_altaz_moonish(testframe): moonaa = moon.transform_to(testframe) # now check that the distance change is similar to earth radius - assert 1000*u.km < np.abs(moonaa.distance - moon.distance).to(u.au) < 7000*u.km + assert 1000 * u.km < np.abs(moonaa.distance - moon.distance).to(u.au) < 7000 * u.km # now check that it round-trips moon2 = moonaa.transform_to(moon) @@ -568,7 +591,7 @@ def test_gcrs_altaz_moonish(testframe): # also should add checks that the alt/az are different for different earth locations -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_gcrs_altaz_bothroutes(testframe): """ Repeat of both the moonish and sunish tests above to make sure the two @@ -576,17 +599,21 @@ def test_gcrs_altaz_bothroutes(testframe): """ sun = get_sun(testframe.obstime) sunaa_viaicrs = sun.transform_to(ICRS()).transform_to(testframe) - sunaa_viaitrs = sun.transform_to(ITRS(obstime=testframe.obstime)).transform_to(testframe) + sunaa_viaitrs = sun.transform_to(ITRS(obstime=testframe.obstime)).transform_to( + testframe + ) moon = GCRS(MOONDIST_CART, obstime=testframe.obstime) moonaa_viaicrs = moon.transform_to(ICRS()).transform_to(testframe) - moonaa_viaitrs = moon.transform_to(ITRS(obstime=testframe.obstime)).transform_to(testframe) + moonaa_viaitrs = moon.transform_to(ITRS(obstime=testframe.obstime)).transform_to( + testframe + ) assert_allclose(sunaa_viaicrs.cartesian.xyz, sunaa_viaitrs.cartesian.xyz) assert_allclose(moonaa_viaicrs.cartesian.xyz, moonaa_viaitrs.cartesian.xyz) -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_cirs_altaz_moonish(testframe): """ Sanity-check that an object resembling the moon goes to the right place with @@ -595,27 +622,29 @@ def test_cirs_altaz_moonish(testframe): moon = CIRS(MOONDIST_CART, obstime=testframe.obstime) moonaa = moon.transform_to(testframe) - assert 1000*u.km < np.abs(moonaa.distance - moon.distance).to(u.km) < 7000*u.km + assert 1000 * u.km < np.abs(moonaa.distance - moon.distance).to(u.km) < 7000 * u.km # now check that it round-trips moon2 = moonaa.transform_to(moon) assert_allclose(moon.cartesian.xyz, moon2.cartesian.xyz) -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_cirs_altaz_nodist(testframe): """ Check that a UnitSphericalRepresentation coordinate round-trips for the CIRS<->AltAz transformation. """ - coo0 = CIRS(UnitSphericalRepresentation(10*u.deg, 20*u.deg), obstime=testframe.obstime) + coo0 = CIRS( + UnitSphericalRepresentation(10 * u.deg, 20 * u.deg), obstime=testframe.obstime + ) # check that it round-trips coo1 = coo0.transform_to(testframe).transform_to(coo0) assert_allclose(coo0.cartesian.xyz, coo1.cartesian.xyz) -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_cirs_icrs_moonish(testframe): """ check that something like the moon goes to about the right distance from the @@ -624,10 +653,10 @@ def test_cirs_icrs_moonish(testframe): moonish = CIRS(MOONDIST_CART, obstime=testframe.obstime) moonicrs = moonish.transform_to(ICRS()) - assert 0.97*u.au < moonicrs.distance < 1.03*u.au + assert 0.97 * u.au < moonicrs.distance < 1.03 * u.au -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_gcrs_icrs_moonish(testframe): """ check that something like the moon goes to about the right distance from the @@ -636,43 +665,45 @@ def test_gcrs_icrs_moonish(testframe): moonish = GCRS(MOONDIST_CART, obstime=testframe.obstime) moonicrs = moonish.transform_to(ICRS()) - assert 0.97*u.au < moonicrs.distance < 1.03*u.au + assert 0.97 * u.au < moonicrs.distance < 1.03 * u.au -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_icrs_gcrscirs_sunish(testframe): """ check that the ICRS barycenter goes to about the right distance from various ~geocentric frames (other than testframe) """ # slight offset to avoid divide-by-zero errors - icrs = ICRS(0*u.deg, 0*u.deg, distance=10*u.km) + icrs = ICRS(0 * u.deg, 0 * u.deg, distance=10 * u.km) gcrs = icrs.transform_to(GCRS(obstime=testframe.obstime)) - assert (EARTHECC - 1)*u.au < gcrs.distance.to(u.au) < (EARTHECC + 1)*u.au + assert (EARTHECC - 1) * u.au < gcrs.distance.to(u.au) < (EARTHECC + 1) * u.au cirs = icrs.transform_to(CIRS(obstime=testframe.obstime)) - assert (EARTHECC - 1)*u.au < cirs.distance.to(u.au) < (EARTHECC + 1)*u.au + assert (EARTHECC - 1) * u.au < cirs.distance.to(u.au) < (EARTHECC + 1) * u.au itrs = icrs.transform_to(ITRS(obstime=testframe.obstime)) - assert (EARTHECC - 1)*u.au < itrs.spherical.distance.to(u.au) < (EARTHECC + 1)*u.au + assert ( + (EARTHECC - 1) * u.au < itrs.spherical.distance.to(u.au) < (EARTHECC + 1) * u.au + ) -@pytest.mark.parametrize('testframe', totest_frames) +@pytest.mark.parametrize("testframe", totest_frames) def test_icrs_altaz_moonish(testframe): """ Check that something expressed in *ICRS* as being moon-like goes to the right AltAz distance """ # we use epv00 instead of get_sun because get_sun includes aberration - earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(testframe.obstime, 'tdb')) - earth_icrs_xyz = earth_pv_bary[0]*u.au - moonoffset = [0, 0, MOONDIST.value]*MOONDIST.unit + earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(testframe.obstime, "tdb")) + earth_icrs_xyz = earth_pv_bary[0] * u.au + moonoffset = [0, 0, MOONDIST.value] * MOONDIST.unit moonish_icrs = ICRS(CartesianRepresentation(earth_icrs_xyz + moonoffset)) moonaa = moonish_icrs.transform_to(testframe) # now check that the distance change is similar to earth radius - assert 1000*u.km < np.abs(moonaa.distance - MOONDIST).to(u.au) < 7000*u.km + assert 1000 * u.km < np.abs(moonaa.distance - MOONDIST).to(u.au) < 7000 * u.km def test_gcrs_self_transform_closeby(): @@ -689,23 +720,32 @@ def test_gcrs_self_transform_closeby(): frame onto the other. """ t = Time("2014-12-25T07:00") - moon_geocentric = SkyCoord(GCRS(318.10579159*u.deg, - -11.65281165*u.deg, - 365042.64880308*u.km, obstime=t)) + moon_geocentric = SkyCoord( + GCRS( + 318.10579159 * u.deg, + -11.65281165 * u.deg, + 365042.64880308 * u.km, + obstime=t, + ) + ) # this is the location of the Moon as seen from La Palma - obsgeoloc = [-5592982.59658935, -63054.1948592, 3059763.90102216]*u.m - obsgeovel = [4.59798494, -407.84677071, 0.]*u.m/u.s - moon_lapalma = SkyCoord(GCRS(318.7048445*u.deg, - -11.98761996*u.deg, - 369722.8231031*u.km, - obstime=t, - obsgeoloc=obsgeoloc, - obsgeovel=obsgeovel)) + obsgeoloc = [-5592982.59658935, -63054.1948592, 3059763.90102216] * u.m + obsgeovel = [4.59798494, -407.84677071, 0.0] * u.m / u.s + moon_lapalma = SkyCoord( + GCRS( + 318.7048445 * u.deg, + -11.98761996 * u.deg, + 369722.8231031 * u.km, + obstime=t, + obsgeoloc=obsgeoloc, + obsgeovel=obsgeovel, + ) + ) transformed = moon_geocentric.transform_to(moon_lapalma.frame) delta = transformed.separation_3d(moon_lapalma) - assert_allclose(delta, 0.0*u.m, atol=1*u.m) + assert_allclose(delta, 0.0 * u.m, atol=1 * u.m) def test_teme_itrf(): @@ -715,44 +755,63 @@ def test_teme_itrf(): Test case derives from example on appendix C of Vallado, Crawford, Hujsak & Kelso (2006). See https://celestrak.com/publications/AIAA/2006-6753/AIAA-2006-6753-Rev2.pdf """ - v_itrf = CartesianDifferential(-3.225636520, -2.872451450, 5.531924446, - unit=u.km/u.s) - p_itrf = CartesianRepresentation(-1033.479383, 7901.2952740, 6380.35659580, - unit=u.km, differentials={'s': v_itrf}) + v_itrf = CartesianDifferential( + -3.225636520, -2.872451450, 5.531924446, unit=u.km / u.s + ) + p_itrf = CartesianRepresentation( + -1033.479383, + 7901.2952740, + 6380.35659580, + unit=u.km, + differentials={"s": v_itrf}, + ) t = Time("2004-04-06T07:51:28.386") teme = ITRS(p_itrf, obstime=t).transform_to(TEME(obstime=t)) - v_teme = CartesianDifferential(-4.746131487, 0.785818041, 5.531931288, - unit=u.km/u.s) - p_teme = CartesianRepresentation(5094.18016210, 6127.64465050, 6380.34453270, - unit=u.km, differentials={'s': v_teme}) + v_teme = CartesianDifferential( + -4.746131487, 0.785818041, 5.531931288, unit=u.km / u.s + ) + p_teme = CartesianRepresentation( + 5094.18016210, + 6127.64465050, + 6380.34453270, + unit=u.km, + differentials={"s": v_teme}, + ) - assert_allclose(teme.cartesian.without_differentials().xyz, - p_teme.without_differentials().xyz, atol=30*u.cm) + assert_allclose( + teme.cartesian.without_differentials().xyz, + p_teme.without_differentials().xyz, + atol=30 * u.cm, + ) - assert_allclose(teme.cartesian.differentials['s'].d_xyz, - p_teme.differentials['s'].d_xyz, atol=1.0*u.cm/u.s) + assert_allclose( + teme.cartesian.differentials["s"].d_xyz, + p_teme.differentials["s"].d_xyz, + atol=1.0 * u.cm / u.s, + ) # test round trip itrf = teme.transform_to(ITRS(obstime=t)) assert_allclose( itrf.cartesian.without_differentials().xyz, p_itrf.without_differentials().xyz, - atol=100*u.cm + atol=100 * u.cm, ) assert_allclose( - itrf.cartesian.differentials['s'].d_xyz, - p_itrf.differentials['s'].d_xyz, - atol=1*u.cm/u.s + itrf.cartesian.differentials["s"].d_xyz, + p_itrf.differentials["s"].d_xyz, + atol=1 * u.cm / u.s, ) def test_precessedgeocentric_loopback(): - from_coo = PrecessedGeocentric(1*u.deg, 2*u.deg, 3*u.AU, - obstime='2001-01-01', equinox='2001-01-01') + from_coo = PrecessedGeocentric( + 1 * u.deg, 2 * u.deg, 3 * u.AU, obstime="2001-01-01", equinox="2001-01-01" + ) # Change just the obstime - to_frame = PrecessedGeocentric(obstime='2001-06-30', equinox='2001-01-01') + to_frame = PrecessedGeocentric(obstime="2001-06-30", equinox="2001-01-01") explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) @@ -768,7 +827,7 @@ def test_precessedgeocentric_loopback(): assert_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10) # Change just the equinox - to_frame = PrecessedGeocentric(obstime='2001-01-01', equinox='2001-06-30') + to_frame = PrecessedGeocentric(obstime="2001-01-01", equinox="2001-06-30") explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) @@ -785,8 +844,8 @@ def test_precessedgeocentric_loopback(): def test_teme_loopback(): - from_coo = TEME(1*u.AU, 2*u.AU, 3*u.AU, obstime='2001-01-01') - to_frame = TEME(obstime='2001-06-30') + from_coo = TEME(1 * u.AU, 2 * u.AU, 3 * u.AU, obstime="2001-01-01") + to_frame = TEME(obstime="2001-06-30") explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) @@ -804,48 +863,53 @@ def test_earth_orientation_table(monkeypatch): Use the here and now to be sure we get a difference. """ - monkeypatch.setattr('astropy.utils.iers.conf.auto_download', True) + monkeypatch.setattr("astropy.utils.iers.conf.auto_download", True) t = Time.now() - location = EarthLocation(lat=0*u.deg, lon=0*u.deg) + location = EarthLocation(lat=0 * u.deg, lon=0 * u.deg) altaz = AltAz(location=location, obstime=t) - sc = SkyCoord(1*u.deg, 2*u.deg) + sc = SkyCoord(1 * u.deg, 2 * u.deg) # Default: uses IERS_Auto, which will give a prediction. # Note: tests run with warnings turned into errors, so it is # meaningful if this passes. if CI: with warnings.catch_warnings(): # Server occasionally blocks IERS download in CI. - warnings.filterwarnings('ignore', message=r'.*using local IERS-B.*') + warnings.filterwarnings("ignore", message=r".*using local IERS-B.*") # This also captures unclosed socket warning that is ignored in setup.cfg - warnings.filterwarnings('ignore', message=r'.*unclosed.*') + warnings.filterwarnings("ignore", message=r".*unclosed.*") altaz_auto = sc.transform_to(altaz) else: altaz_auto = sc.transform_to(altaz) # No warnings with iers.earth_orientation_table.set(iers.IERS_B.open()): - with pytest.warns(AstropyWarning, match='after IERS data'): + with pytest.warns(AstropyWarning, match="after IERS data"): altaz_b = sc.transform_to(altaz) sep_b_auto = altaz_b.separation(altaz_auto) - assert_allclose(sep_b_auto, 0.0*u.deg, atol=1*u.arcsec) - assert sep_b_auto > 10*u.microarcsecond + assert_allclose(sep_b_auto, 0.0 * u.deg, atol=1 * u.arcsec) + assert sep_b_auto > 10 * u.microarcsecond # Check we returned to regular IERS system. altaz_auto2 = sc.transform_to(altaz) - assert altaz_auto2.separation(altaz_auto) == 0. + assert altaz_auto2.separation(altaz_auto) == 0.0 @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_ephemerides(): """ We test that using different ephemerides gives very similar results for transformations """ t = Time("2014-12-25T07:00") - moon = SkyCoord(GCRS(318.10579159*u.deg, - -11.65281165*u.deg, - 365042.64880308*u.km, obstime=t)) + moon = SkyCoord( + GCRS( + 318.10579159 * u.deg, + -11.65281165 * u.deg, + 365042.64880308 * u.km, + obstime=t, + ) + ) icrs_frame = ICRS() hcrs_frame = HCRS(obstime=t) @@ -857,7 +921,7 @@ def test_ephemerides(): moon_helioecl_builtin = moon.transform_to(ecl_frame) moon_cirs_builtin = moon.transform_to(cirs_frame) - with solar_system_ephemeris.set('jpl'): + with solar_system_ephemeris.set("jpl"): moon_icrs_jpl = moon.transform_to(icrs_frame) moon_hcrs_jpl = moon.transform_to(hcrs_frame) moon_helioecl_jpl = moon.transform_to(ecl_frame) @@ -870,11 +934,13 @@ def test_ephemerides(): sep_helioecl = moon_helioecl_builtin.separation(moon_helioecl_jpl) sep_cirs = moon_cirs_builtin.separation(moon_cirs_jpl) - assert_allclose([sep_icrs, sep_hcrs, sep_helioecl], 0.0*u.deg, atol=10*u.mas) - assert all(sep > 10*u.microarcsecond for sep in (sep_icrs, sep_hcrs, sep_helioecl)) + assert_allclose([sep_icrs, sep_hcrs, sep_helioecl], 0.0 * u.deg, atol=10 * u.mas) + assert all( + sep > 10 * u.microarcsecond for sep in (sep_icrs, sep_hcrs, sep_helioecl) + ) # CIRS should be the same - assert_allclose(sep_cirs, 0.0*u.deg, atol=1*u.microarcsecond) + assert_allclose(sep_cirs, 0.0 * u.deg, atol=1 * u.microarcsecond) def test_tete_transforms(): @@ -885,40 +951,45 @@ def test_tete_transforms(): test_solar_system.py. Here we are looking to check for consistency and errors in the self transform. """ - loc = EarthLocation.from_geodetic("-22°57'35.1", "-67°47'14.1", 5186*u.m) - time = Time('2020-04-06T00:00') + loc = EarthLocation.from_geodetic("-22°57'35.1", "-67°47'14.1", 5186 * u.m) + time = Time("2020-04-06T00:00") p, v = loc.get_gcrs_posvel(time) gcrs_frame = GCRS(obstime=time, obsgeoloc=p, obsgeovel=v) - moon = SkyCoord(169.24113968*u.deg, 10.86086666*u.deg, 358549.25381755*u.km, frame=gcrs_frame) + moon = SkyCoord( + 169.24113968 * u.deg, + 10.86086666 * u.deg, + 358549.25381755 * u.km, + frame=gcrs_frame, + ) tete_frame = TETE(obstime=time, location=loc) # need to set obsgeoloc/vel explicitly or skycoord behaviour over-writes - tete_geo = TETE(obstime=time, location=EarthLocation(*([0, 0, 0]*u.km))) + tete_geo = TETE(obstime=time, location=EarthLocation(*([0, 0, 0] * u.km))) # test self-transform by comparing to GCRS-TETE-ITRS-TETE route tete_coo1 = moon.transform_to(tete_frame) tete_coo2 = moon.transform_to(tete_geo) - assert_allclose(tete_coo1.separation_3d(tete_coo2), 0*u.mm, atol=1*u.mm) + assert_allclose(tete_coo1.separation_3d(tete_coo2), 0 * u.mm, atol=1 * u.mm) # test TETE-ITRS transform by comparing GCRS-CIRS-ITRS to GCRS-TETE-ITRS itrs1 = moon.transform_to(CIRS()).transform_to(ITRS()) itrs2 = moon.transform_to(TETE()).transform_to(ITRS()) - assert_allclose(itrs1.separation_3d(itrs2), 0*u.mm, atol=1*u.mm) + assert_allclose(itrs1.separation_3d(itrs2), 0 * u.mm, atol=1 * u.mm) # test round trip GCRS->TETE->GCRS new_moon = moon.transform_to(TETE()).transform_to(moon) - assert_allclose(new_moon.separation_3d(moon), 0*u.mm, atol=1*u.mm) + assert_allclose(new_moon.separation_3d(moon), 0 * u.mm, atol=1 * u.mm) # test round trip via ITRS tete_rt = tete_coo1.transform_to(ITRS(obstime=time)).transform_to(tete_coo1) - assert_allclose(tete_rt.separation_3d(tete_coo1), 0*u.mm, atol=1*u.mm) + assert_allclose(tete_rt.separation_3d(tete_coo1), 0 * u.mm, atol=1 * u.mm) # ensure deprecated routine remains consistent # make sure test raises warning! - with pytest.warns(AstropyDeprecationWarning, match='The use of'): + with pytest.warns(AstropyDeprecationWarning, match="The use of"): tete_alt = _apparent_position_in_true_coordinates(moon) - assert_allclose(tete_coo1.separation_3d(tete_alt), 0*u.mm, atol=100*u.mm) + assert_allclose(tete_coo1.separation_3d(tete_alt), 0 * u.mm, atol=100 * u.mm) def test_straight_overhead(): @@ -927,9 +998,9 @@ def test_straight_overhead(): If the CIRS self-transform breaks it won't, due to improper treatment of aberration """ - t = Time('J2010') - obj = EarthLocation(-1*u.deg, 52*u.deg, height=10.*u.km) - home = EarthLocation(-1*u.deg, 52*u.deg, height=0.*u.km) + t = Time("J2010") + obj = EarthLocation(-1 * u.deg, 52 * u.deg, height=10.0 * u.km) + home = EarthLocation(-1 * u.deg, 52 * u.deg, height=0.0 * u.km) # An object that appears straight overhead - FOR A GEOCENTRIC OBSERVER. # Note, this won't be overhead for a topocentric observer because of @@ -948,12 +1019,12 @@ def test_straight_overhead(): # Check AltAz (though Azimuth can be anything so is not tested). aa = cirs_topo.transform_to(AltAz(obstime=t, location=home)) - assert_allclose(aa.alt, 90*u.deg, atol=1*u.uas, rtol=0) + assert_allclose(aa.alt, 90 * u.deg, atol=1 * u.uas, rtol=0) # Check HADec. hd = cirs_topo.transform_to(HADec(obstime=t, location=home)) - assert_allclose(hd.ha, 0*u.hourangle, atol=1*u.uas, rtol=0) - assert_allclose(hd.dec, 52*u.deg, atol=1*u.uas, rtol=0) + assert_allclose(hd.ha, 0 * u.hourangle, atol=1 * u.uas, rtol=0) + assert_allclose(hd.dec, 52 * u.deg, atol=1 * u.uas, rtol=0) def test_itrs_straight_overhead(): @@ -961,9 +1032,9 @@ def test_itrs_straight_overhead(): With a precise ITRS<->Observed transformation this should give Alt=90 exactly """ - t = Time('J2010') - obj = EarthLocation(-1*u.deg, 52*u.deg, height=10.*u.km) - home = EarthLocation(-1*u.deg, 52*u.deg, height=0.*u.km) + t = Time("J2010") + obj = EarthLocation(-1 * u.deg, 52 * u.deg, height=10.0 * u.km) + home = EarthLocation(-1 * u.deg, 52 * u.deg, height=0.0 * u.km) # An object that appears straight overhead - FOR A GEOCENTRIC OBSERVER. itrs_geo = obj.get_itrs(t).cartesian @@ -979,12 +1050,12 @@ def test_itrs_straight_overhead(): # Check AltAz (though Azimuth can be anything so is not tested). aa = itrs_topo.transform_to(AltAz(obstime=t, location=home)) - assert_allclose(aa.alt, 90*u.deg, atol=1*u.uas, rtol=0) + assert_allclose(aa.alt, 90 * u.deg, atol=1 * u.uas, rtol=0) # Check HADec. hd = itrs_topo.transform_to(HADec(obstime=t, location=home)) - assert_allclose(hd.ha, 0*u.hourangle, atol=1*u.uas, rtol=0) - assert_allclose(hd.dec, 52*u.deg, atol=1*u.uas, rtol=0) + assert_allclose(hd.ha, 0 * u.hourangle, atol=1 * u.uas, rtol=0) + assert_allclose(hd.dec, 52 * u.deg, atol=1 * u.uas, rtol=0) def jplephem_ge(minversion): @@ -993,13 +1064,13 @@ def jplephem_ge(minversion): # not HAS_JPLEPHEM or metadata.version('jplephem') < '2.15' # leads to a module not found error. try: - return HAS_JPLEPHEM and metadata.version('jplephem') >= minversion + return HAS_JPLEPHEM and metadata.version("jplephem") >= minversion except Exception: return False @pytest.mark.remote_data -@pytest.mark.skipif(not jplephem_ge('2.15'), reason='requires jplephem >= 2.15') +@pytest.mark.skipif(not jplephem_ge("2.15"), reason="requires jplephem >= 2.15") def test_aa_hd_high_precision(): """These tests are provided by @mkbrewer - see issue #10356. @@ -1019,32 +1090,35 @@ def test_aa_hd_high_precision(): due to an improvement (e.g., a new IAU precession model). """ - lat = -22.959748*u.deg - lon = -67.787260*u.deg - elev = 5186*u.m + lat = -22.959748 * u.deg + lon = -67.787260 * u.deg + elev = 5186 * u.m loc = EarthLocation.from_geodetic(lon, lat, elev) # Note: at this level of precision for the comparison, we have to include # the location in the time, as it influences the transformation to TDB. - t = Time('2017-04-06T00:00:00.0', location=loc) - with solar_system_ephemeris.set('de430'): - moon = get_body('moon', t, loc) + t = Time("2017-04-06T00:00:00.0", location=loc) + with solar_system_ephemeris.set("de430"): + moon = get_body("moon", t, loc) moon_aa = moon.transform_to(AltAz(obstime=t, location=loc)) moon_hd = moon.transform_to(HADec(obstime=t, location=loc)) # Numbers from # https://github.com/astropy/astropy/pull/11073#issuecomment-735486271 # updated in https://github.com/astropy/astropy/issues/11683 - TARGET_AZ, TARGET_EL = 15.032673509956*u.deg, 50.303110133923*u.deg - TARGET_DISTANCE = 376252883.247239*u.m - assert_allclose(moon_aa.az, TARGET_AZ, atol=0.1*u.uas, rtol=0) - assert_allclose(moon_aa.alt, TARGET_EL, atol=0.1*u.uas, rtol=0) - assert_allclose(moon_aa.distance, TARGET_DISTANCE, atol=0.1*u.mm, rtol=0) - ha, dec = erfa.ae2hd(moon_aa.az.to_value(u.radian), moon_aa.alt.to_value(u.radian), - lat.to_value(u.radian)) + TARGET_AZ, TARGET_EL = 15.032673509956 * u.deg, 50.303110133923 * u.deg + TARGET_DISTANCE = 376252883.247239 * u.m + assert_allclose(moon_aa.az, TARGET_AZ, atol=0.1 * u.uas, rtol=0) + assert_allclose(moon_aa.alt, TARGET_EL, atol=0.1 * u.uas, rtol=0) + assert_allclose(moon_aa.distance, TARGET_DISTANCE, atol=0.1 * u.mm, rtol=0) + ha, dec = erfa.ae2hd( + moon_aa.az.to_value(u.radian), + moon_aa.alt.to_value(u.radian), + lat.to_value(u.radian), + ) ha = u.Quantity(ha, u.radian, copy=False) dec = u.Quantity(dec, u.radian, copy=False) - assert_allclose(moon_hd.ha, ha, atol=0.1*u.uas, rtol=0) - assert_allclose(moon_hd.dec, dec, atol=0.1*u.uas, rtol=0) + assert_allclose(moon_hd.ha, ha, atol=0.1 * u.uas, rtol=0) + assert_allclose(moon_hd.dec, dec, atol=0.1 * u.uas, rtol=0) def test_aa_high_precision_nodata(): @@ -1056,17 +1130,17 @@ def test_aa_high_precision_nodata(): ephemerides to avoid the use of remote data. """ # Last updated when switching to erfa 2.0.0 and its moon98 function. - TARGET_AZ, TARGET_EL = 15.03231495*u.deg, 50.3027193*u.deg - lat = -22.959748*u.deg - lon = -67.787260*u.deg - elev = 5186*u.m + TARGET_AZ, TARGET_EL = 15.03231495 * u.deg, 50.3027193 * u.deg + lat = -22.959748 * u.deg + lon = -67.787260 * u.deg + elev = 5186 * u.m loc = EarthLocation.from_geodetic(lon, lat, elev) - t = Time('2017-04-06T00:00:00.0') + t = Time("2017-04-06T00:00:00.0") - moon = get_body('moon', t, loc) + moon = get_body("moon", t, loc) moon_aa = moon.transform_to(AltAz(obstime=t, location=loc)) - assert_allclose(moon_aa.az - TARGET_AZ, 0*u.mas, atol=0.5*u.mas) - assert_allclose(moon_aa.alt - TARGET_EL, 0*u.mas, atol=0.5*u.mas) + assert_allclose(moon_aa.az - TARGET_AZ, 0 * u.mas, atol=0.5 * u.mas) + assert_allclose(moon_aa.alt - TARGET_EL, 0 * u.mas, atol=0.5 * u.mas) class TestGetLocationGCRS: @@ -1075,20 +1149,23 @@ class TestGetLocationGCRS: # with a direct transformation. def setup_class(cls): cls.loc = loc = EarthLocation.from_geodetic( - np.linspace(0, 360, 6)*u.deg, np.linspace(-90, 90, 6)*u.deg, 100*u.m) - cls.obstime = obstime = Time(np.linspace(2000, 2010, 6), format='jyear') + np.linspace(0, 360, 6) * u.deg, np.linspace(-90, 90, 6) * u.deg, 100 * u.m + ) + cls.obstime = obstime = Time(np.linspace(2000, 2010, 6), format="jyear") # Get comparison via a full transformation. We do not use any methods # of EarthLocation, since those depend on the fast transform. loc_itrs = ITRS(loc.x, loc.y, loc.z, obstime=obstime) - zeros = np.broadcast_to(0. * (u.km / u.s), (3,) + loc_itrs.shape, subok=True) - loc_itrs.data.differentials['s'] = CartesianDifferential(zeros) + zeros = np.broadcast_to(0.0 * (u.km / u.s), (3,) + loc_itrs.shape, subok=True) + loc_itrs.data.differentials["s"] = CartesianDifferential(zeros) loc_gcrs_cart = loc_itrs.transform_to(GCRS(obstime=obstime)).cartesian cls.obsgeoloc = loc_gcrs_cart.without_differentials() - cls.obsgeovel = loc_gcrs_cart.differentials['s'].to_cartesian() + cls.obsgeovel = loc_gcrs_cart.differentials["s"].to_cartesian() def check_obsgeo(self, obsgeoloc, obsgeovel): - assert_allclose(obsgeoloc.xyz, self.obsgeoloc.xyz, atol=.1*u.um, rtol=0.) - assert_allclose(obsgeovel.xyz, self.obsgeovel.xyz, atol=.1*u.mm/u.s, rtol=0.) + assert_allclose(obsgeoloc.xyz, self.obsgeoloc.xyz, atol=0.1 * u.um, rtol=0.0) + assert_allclose( + obsgeovel.xyz, self.obsgeovel.xyz, atol=0.1 * u.mm / u.s, rtol=0.0 + ) def test_get_gcrs_posvel(self): # Really just a sanity check @@ -1096,16 +1173,17 @@ def test_get_gcrs_posvel(self): def test_tete_quick(self): # Following copied from intermediate_rotation_transforms.gcrs_to_tete - rbpn = erfa.pnm06a(*get_jd12(self.obstime, 'tt')) - loc_gcrs_frame = get_location_gcrs(self.loc, self.obstime, - tete_to_itrs_mat(self.obstime, rbpn=rbpn), - rbpn) + rbpn = erfa.pnm06a(*get_jd12(self.obstime, "tt")) + loc_gcrs_frame = get_location_gcrs( + self.loc, self.obstime, tete_to_itrs_mat(self.obstime, rbpn=rbpn), rbpn + ) self.check_obsgeo(loc_gcrs_frame.obsgeoloc, loc_gcrs_frame.obsgeovel) def test_cirs_quick(self): cirs_frame = CIRS(location=self.loc, obstime=self.obstime) # Following copied from intermediate_rotation_transforms.gcrs_to_cirs pmat = gcrs_to_cirs_mat(cirs_frame.obstime) - loc_gcrs_frame = get_location_gcrs(self.loc, self.obstime, - cirs_to_itrs_mat(cirs_frame.obstime), pmat) + loc_gcrs_frame = get_location_gcrs( + self.loc, self.obstime, cirs_to_itrs_mat(cirs_frame.obstime), pmat + ) self.check_obsgeo(loc_gcrs_frame.obsgeoloc, loc_gcrs_frame.obsgeovel) diff --git a/astropy/coordinates/tests/test_matching.py b/astropy/coordinates/tests/test_matching.py index 380e7f7022c..b642cac437b 100644 --- a/astropy/coordinates/tests/test_matching.py +++ b/astropy/coordinates/tests/test_matching.py @@ -23,8 +23,8 @@ def test_matching_function(): # this only uses match_coordinates_3d because that's the actual implementation - cmatch = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree) - ccatalog = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree) + cmatch = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree) + ccatalog = ICRS([1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree) idx, d2d, d3d = match_coordinates_3d(cmatch, ccatalog) npt.assert_array_equal(idx, [3, 1]) @@ -42,15 +42,17 @@ def test_matching_function_3d_and_sky(): from astropy.coordinates import ICRS from astropy.coordinates.matching import match_coordinates_3d, match_coordinates_sky - cmatch = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 5] * u.kpc) - ccatalog = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree, distance=[1, 1, 1, 5] * u.kpc) + cmatch = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 5] * u.kpc) + ccatalog = ICRS( + [1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree, distance=[1, 1, 1, 5] * u.kpc + ) idx, d2d, d3d = match_coordinates_3d(cmatch, ccatalog) npt.assert_array_equal(idx, [2, 3]) assert_allclose(d2d, [1, 1.9] * u.deg) assert np.abs(d3d[0].to_value(u.kpc) - np.radians(1)) < 1e-6 - assert np.abs(d3d[1].to_value(u.kpc) - 5*np.radians(1.9)) < 1e-5 + assert np.abs(d3d[1].to_value(u.kpc) - 5 * np.radians(1.9)) < 1e-5 idx, d2d, d3d = match_coordinates_sky(cmatch, ccatalog) npt.assert_array_equal(idx, [3, 1]) @@ -59,66 +61,75 @@ def test_matching_function_3d_and_sky(): assert_allclose(d3d, [4, 4.0000019] * u.kpc) -@pytest.mark.parametrize('functocheck, args, defaultkdtname, bothsaved', - [(matching.match_coordinates_3d, [], 'kdtree_3d', False), - (matching.match_coordinates_sky, [], 'kdtree_sky', False), - (matching.search_around_3d, [1*u.kpc], 'kdtree_3d', True), - (matching.search_around_sky, [1*u.deg], 'kdtree_sky', False) - ]) +@pytest.mark.parametrize( + "functocheck, args, defaultkdtname, bothsaved", + [ + (matching.match_coordinates_3d, [], "kdtree_3d", False), + (matching.match_coordinates_sky, [], "kdtree_sky", False), + (matching.search_around_3d, [1 * u.kpc], "kdtree_3d", True), + (matching.search_around_sky, [1 * u.deg], "kdtree_sky", False), + ], +) @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy.") def test_kdtree_storage(functocheck, args, defaultkdtname, bothsaved): from astropy.coordinates import ICRS def make_scs(): - cmatch = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 2]*u.kpc) - ccatalog = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree, distance=[1, 2, 3, 4]*u.kpc) + cmatch = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 2] * u.kpc) + ccatalog = ICRS( + [1, 2, 3, 4] * u.degree, + [0, 0, 0, 0] * u.degree, + distance=[1, 2, 3, 4] * u.kpc, + ) return cmatch, ccatalog cmatch, ccatalog = make_scs() functocheck(cmatch, ccatalog, *args, storekdtree=False) - assert 'kdtree' not in ccatalog.cache + assert "kdtree" not in ccatalog.cache assert defaultkdtname not in ccatalog.cache cmatch, ccatalog = make_scs() functocheck(cmatch, ccatalog, *args) assert defaultkdtname in ccatalog.cache - assert 'kdtree' not in ccatalog.cache + assert "kdtree" not in ccatalog.cache cmatch, ccatalog = make_scs() functocheck(cmatch, ccatalog, *args, storekdtree=True) - assert 'kdtree' in ccatalog.cache + assert "kdtree" in ccatalog.cache assert defaultkdtname not in ccatalog.cache cmatch, ccatalog = make_scs() - assert 'tislit_cheese' not in ccatalog.cache - functocheck(cmatch, ccatalog, *args, storekdtree='tislit_cheese') - assert 'tislit_cheese' in ccatalog.cache + assert "tislit_cheese" not in ccatalog.cache + functocheck(cmatch, ccatalog, *args, storekdtree="tislit_cheese") + assert "tislit_cheese" in ccatalog.cache assert defaultkdtname not in ccatalog.cache - assert 'kdtree' not in ccatalog.cache + assert "kdtree" not in ccatalog.cache if bothsaved: - assert 'tislit_cheese' in cmatch.cache + assert "tislit_cheese" in cmatch.cache assert defaultkdtname not in cmatch.cache - assert 'kdtree' not in cmatch.cache + assert "kdtree" not in cmatch.cache else: - assert 'tislit_cheese' not in cmatch.cache + assert "tislit_cheese" not in cmatch.cache # now a bit of a hacky trick to make sure it at least tries to *use* it - ccatalog.cache['tislit_cheese'] = 1 - cmatch.cache['tislit_cheese'] = 1 + ccatalog.cache["tislit_cheese"] = 1 + cmatch.cache["tislit_cheese"] = 1 with pytest.raises(TypeError) as e: - functocheck(cmatch, ccatalog, *args, storekdtree='tislit_cheese') - assert 'KD' in e.value.args[0] + functocheck(cmatch, ccatalog, *args, storekdtree="tislit_cheese") + assert "KD" in e.value.args[0] @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy.") def test_python_kdtree(monkeypatch): from astropy.coordinates import ICRS - cmatch = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 2]*u.kpc) - ccatalog = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree, distance=[1, 2, 3, 4]*u.kpc) + cmatch = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 2] * u.kpc) + ccatalog = ICRS( + [1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree, distance=[1, 2, 3, 4] * u.kpc + ) monkeypatch.delattr("scipy.spatial.cKDTree") - with pytest.warns(UserWarning, match=r'C-based KD tree not found'): + with pytest.warns(UserWarning, match=r"C-based KD tree not found"): matching.match_coordinates_sky(cmatch, ccatalog) @@ -129,10 +140,14 @@ def test_matching_method(): from astropy.utils import NumpyRNGContext with NumpyRNGContext(987654321): - cmatch = ICRS(np.random.rand(20) * 360.*u.degree, - (np.random.rand(20) * 180. - 90.)*u.degree) - ccatalog = ICRS(np.random.rand(100) * 360. * u.degree, - (np.random.rand(100) * 180. - 90.)*u.degree) + cmatch = ICRS( + np.random.rand(20) * 360.0 * u.degree, + (np.random.rand(20) * 180.0 - 90.0) * u.degree, + ) + ccatalog = ICRS( + np.random.rand(100) * 360.0 * u.degree, + (np.random.rand(100) * 180.0 - 90.0) * u.degree, + ) idx1, d2d1, d3d1 = SkyCoord(cmatch).match_to_catalog_3d(ccatalog) idx2, d2d2, d3d2 = match_coordinates_3d(cmatch, ccatalog) @@ -157,33 +172,39 @@ def test_search_around(): from astropy.coordinates import ICRS, SkyCoord from astropy.coordinates.matching import search_around_3d, search_around_sky - coo1 = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 5] * u.kpc) - coo2 = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree, distance=[1, 1, 1, 5] * u.kpc) + coo1 = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 5] * u.kpc) + coo2 = ICRS( + [1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree, distance=[1, 1, 1, 5] * u.kpc + ) - idx1_1deg, idx2_1deg, d2d_1deg, d3d_1deg = search_around_sky(coo1, coo2, 1.01*u.deg) - idx1_0p05deg, idx2_0p05deg, d2d_0p05deg, d3d_0p05deg = search_around_sky(coo1, coo2, 0.05*u.deg) + idx1_1deg, idx2_1deg, d2d_1deg, d3d_1deg = search_around_sky( + coo1, coo2, 1.01 * u.deg + ) + idx1_0p05deg, idx2_0p05deg, d2d_0p05deg, d3d_0p05deg = search_around_sky( + coo1, coo2, 0.05 * u.deg + ) assert list(zip(idx1_1deg, idx2_1deg)) == [(0, 2), (0, 3), (1, 1), (1, 2)] - assert_allclose(d2d_1deg[0], 1.0*u.deg, atol=1e-14*u.deg, rtol=0) - assert_allclose(d2d_1deg, [1, 0, .1, .9]*u.deg) + assert_allclose(d2d_1deg[0], 1.0 * u.deg, atol=1e-14 * u.deg, rtol=0) + assert_allclose(d2d_1deg, [1, 0, 0.1, 0.9] * u.deg) assert list(zip(idx1_0p05deg, idx2_0p05deg)) == [(0, 3)] - idx1_1kpc, idx2_1kpc, d2d_1kpc, d3d_1kpc = search_around_3d(coo1, coo2, 1*u.kpc) - idx1_sm, idx2_sm, d2d_sm, d3d_sm = search_around_3d(coo1, coo2, 0.05*u.kpc) + idx1_1kpc, idx2_1kpc, d2d_1kpc, d3d_1kpc = search_around_3d(coo1, coo2, 1 * u.kpc) + idx1_sm, idx2_sm, d2d_sm, d3d_sm = search_around_3d(coo1, coo2, 0.05 * u.kpc) assert list(zip(idx1_1kpc, idx2_1kpc)) == [(0, 0), (0, 1), (0, 2), (1, 3)] assert list(zip(idx1_sm, idx2_sm)) == [(0, 1), (0, 2)] - assert_allclose(d2d_sm, [2, 1]*u.deg) + assert_allclose(d2d_sm, [2, 1] * u.deg) # Test for the non-matches, #4877 - coo1 = ICRS([4.1, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 5] * u.kpc) - idx1, idx2, d2d, d3d = search_around_sky(coo1, coo2, 1*u.arcsec) + coo1 = ICRS([4.1, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 5] * u.kpc) + idx1, idx2, d2d, d3d = search_around_sky(coo1, coo2, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc - idx1, idx2, d2d, d3d = search_around_3d(coo1, coo2, 1*u.m) + idx1, idx2, d2d, d3d = search_around_3d(coo1, coo2, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg @@ -191,33 +212,33 @@ def test_search_around(): # Test when one or both of the coordinate arrays is empty, #4875 empty = ICRS(ra=[] * u.degree, dec=[] * u.degree, distance=[] * u.kpc) - idx1, idx2, d2d, d3d = search_around_sky(empty, coo2, 1*u.arcsec) + idx1, idx2, d2d, d3d = search_around_sky(empty, coo2, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc - idx1, idx2, d2d, d3d = search_around_sky(coo1, empty, 1*u.arcsec) + idx1, idx2, d2d, d3d = search_around_sky(coo1, empty, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc empty = ICRS(ra=[] * u.degree, dec=[] * u.degree, distance=[] * u.kpc) - idx1, idx2, d2d, d3d = search_around_sky(empty, empty[:], 1*u.arcsec) + idx1, idx2, d2d, d3d = search_around_sky(empty, empty[:], 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc - idx1, idx2, d2d, d3d = search_around_3d(empty, coo2, 1*u.m) + idx1, idx2, d2d, d3d = search_around_3d(empty, coo2, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc - idx1, idx2, d2d, d3d = search_around_3d(coo1, empty, 1*u.m) + idx1, idx2, d2d, d3d = search_around_3d(coo1, empty, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg assert d3d.unit == u.kpc - idx1, idx2, d2d, d3d = search_around_3d(empty, empty[:], 1*u.m) + idx1, idx2, d2d, d3d = search_around_3d(empty, empty[:], 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == int assert d2d.unit == u.deg @@ -226,10 +247,10 @@ def test_search_around(): # Test that input without distance units results in a # 'dimensionless_unscaled' unit cempty = SkyCoord(ra=[], dec=[], unit=u.deg) - idx1, idx2, d2d, d3d = search_around_3d(cempty, cempty[:], 1*u.m) + idx1, idx2, d2d, d3d = search_around_3d(cempty, cempty[:], 1 * u.m) assert d2d.unit == u.deg assert d3d.unit == u.dimensionless_unscaled - idx1, idx2, d2d, d3d = search_around_sky(cempty, cempty[:], 1*u.m) + idx1, idx2, d2d, d3d = search_around_sky(cempty, cempty[:], 1 * u.m) assert d2d.unit == u.deg assert d3d.unit == u.dimensionless_unscaled @@ -239,18 +260,18 @@ def test_search_around_scalar(): from astropy.coordinates import Angle, SkyCoord cat = SkyCoord([1, 2, 3], [-30, 45, 8], unit="deg") - target = SkyCoord('1.1 -30.1', unit="deg") + target = SkyCoord("1.1 -30.1", unit="deg") with pytest.raises(ValueError) as excinfo: - cat.search_around_sky(target, Angle('2d')) + cat.search_around_sky(target, Angle("2d")) # make sure the error message is *specific* to search_around_sky rather than # generic as reported in #3359 - assert 'search_around_sky' in str(excinfo.value) + assert "search_around_sky" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: - cat.search_around_3d(target, Angle('2d')) - assert 'search_around_3d' in str(excinfo.value) + cat.search_around_3d(target, Angle("2d")) + assert "search_around_3d" in str(excinfo.value) @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") @@ -270,22 +291,21 @@ def test_match_catalog_empty(): with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_sky(cat1[0]) - assert 'catalog' in str(excinfo.value) + assert "catalog" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_3d(cat1[0]) - assert 'catalog' in str(excinfo.value) + assert "catalog" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_sky(cat0) - assert 'catalog' in str(excinfo.value) + assert "catalog" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_3d(cat0) - assert 'catalog' in str(excinfo.value) + assert "catalog" in str(excinfo.value) @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") -@pytest.mark.filterwarnings( - r'ignore:invalid value encountered in.*:RuntimeWarning') +@pytest.mark.filterwarnings(r"ignore:invalid value encountered in.*:RuntimeWarning") def test_match_catalog_nan(): from astropy.coordinates import Galactic, SkyCoord @@ -294,28 +314,28 @@ def test_match_catalog_nan(): cat = SkyCoord([1.1, 3], [2.1, 5], unit="deg") cat_with_nans = SkyCoord([1.1, np.nan], [2.1, 5], unit="deg") - galcat_with_nans = Galactic([1.2, np.nan]*u.deg, [5.6, 7.8]*u.deg) + galcat_with_nans = Galactic([1.2, np.nan] * u.deg, [5.6, 7.8] * u.deg) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_sky(cat_with_nans) - assert 'Catalog coordinates cannot contain' in str(excinfo.value) + assert "Catalog coordinates cannot contain" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_3d(cat_with_nans) - assert 'Catalog coordinates cannot contain' in str(excinfo.value) + assert "Catalog coordinates cannot contain" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_sky(galcat_with_nans) - assert 'Catalog coordinates cannot contain' in str(excinfo.value) + assert "Catalog coordinates cannot contain" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc1.match_to_catalog_3d(galcat_with_nans) - assert 'Catalog coordinates cannot contain' in str(excinfo.value) + assert "Catalog coordinates cannot contain" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc_with_nans.match_to_catalog_sky(cat) - assert 'Matching coordinates cannot contain' in str(excinfo.value) + assert "Matching coordinates cannot contain" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: sc_with_nans.match_to_catalog_3d(cat) - assert 'Matching coordinates cannot contain' in str(excinfo.value) + assert "Matching coordinates cannot contain" in str(excinfo.value) @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") @@ -326,4 +346,4 @@ def test_match_catalog_nounit(): i1 = ICRS([[1], [2], [3]], representation_type=CartesianRepresentation) i2 = ICRS([[1], [2], [4, 5]], representation_type=CartesianRepresentation) i, sep, sep3d = match_coordinates_sky(i1, i2) - assert_allclose(sep3d, [1]*u.dimensionless_unscaled) + assert_allclose(sep3d, [1] * u.dimensionless_unscaled) diff --git a/astropy/coordinates/tests/test_matrix_utilities.py b/astropy/coordinates/tests/test_matrix_utilities.py index ae89cf431b9..ab0f9a7d339 100644 --- a/astropy/coordinates/tests/test_matrix_utilities.py +++ b/astropy/coordinates/tests/test_matrix_utilities.py @@ -15,49 +15,59 @@ def test_rotation_matrix(): - assert_array_equal(rotation_matrix(0*u.deg, 'x'), np.eye(3)) - - assert_allclose(rotation_matrix(90*u.deg, 'y'), [[0, 0, -1], - [0, 1, 0], - [1, 0, 0]], atol=1e-12) - - assert_allclose(rotation_matrix(-90*u.deg, 'z'), [[0, -1, 0], - [1, 0, 0], - [0, 0, 1]], atol=1e-12) - - assert_allclose(rotation_matrix(45*u.deg, 'x'), - rotation_matrix(45*u.deg, [1, 0, 0])) - assert_allclose(rotation_matrix(125*u.deg, 'y'), - rotation_matrix(125*u.deg, [0, 1, 0])) - assert_allclose(rotation_matrix(-30*u.deg, 'z'), - rotation_matrix(-30*u.deg, [0, 0, 1])) - - assert_allclose(np.dot(rotation_matrix(180*u.deg, [1, 1, 0]), [1, 0, 0]), - [0, 1, 0], atol=1e-12) + assert_array_equal(rotation_matrix(0 * u.deg, "x"), np.eye(3)) + + assert_allclose( + rotation_matrix(90 * u.deg, "y"), [[0, 0, -1], [0, 1, 0], [1, 0, 0]], atol=1e-12 + ) + + assert_allclose( + rotation_matrix(-90 * u.deg, "z"), + [[0, -1, 0], [1, 0, 0], [0, 0, 1]], + atol=1e-12, + ) + + assert_allclose( + rotation_matrix(45 * u.deg, "x"), rotation_matrix(45 * u.deg, [1, 0, 0]) + ) + assert_allclose( + rotation_matrix(125 * u.deg, "y"), rotation_matrix(125 * u.deg, [0, 1, 0]) + ) + assert_allclose( + rotation_matrix(-30 * u.deg, "z"), rotation_matrix(-30 * u.deg, [0, 0, 1]) + ) + + assert_allclose( + np.dot(rotation_matrix(180 * u.deg, [1, 1, 0]), [1, 0, 0]), + [0, 1, 0], + atol=1e-12, + ) # make sure it also works for very small angles - assert_allclose(rotation_matrix(0.000001*u.deg, 'x'), - rotation_matrix(0.000001*u.deg, [1, 0, 0])) + assert_allclose( + rotation_matrix(0.000001 * u.deg, "x"), + rotation_matrix(0.000001 * u.deg, [1, 0, 0]), + ) def test_angle_axis(): - m1 = rotation_matrix(35*u.deg, 'x') + m1 = rotation_matrix(35 * u.deg, "x") an1, ax1 = angle_axis(m1) - assert an1 - 35*u.deg < 1e-10*u.deg + assert an1 - 35 * u.deg < 1e-10 * u.deg assert_allclose(ax1, [1, 0, 0]) - m2 = rotation_matrix(-89*u.deg, [1, 1, 0]) + m2 = rotation_matrix(-89 * u.deg, [1, 1, 0]) an2, ax2 = angle_axis(m2) - assert an2 - 89*u.deg < 1e-10*u.deg - assert_allclose(ax2, [-2**-0.5, -2**-0.5, 0]) + assert an2 - 89 * u.deg < 1e-10 * u.deg + assert_allclose(ax2, [-(2**-0.5), -(2**-0.5), 0]) def test_is_O3(): """Test the matrix checker ``is_O3``.""" # Normal rotation matrix - m1 = rotation_matrix(35*u.deg, 'x') + m1 = rotation_matrix(35 * u.deg, "x") assert is_O3(m1) # and (M, 3, 3) n1 = np.tile(m1, (2, 1, 1)) @@ -65,7 +75,7 @@ def test_is_O3(): # reflection m2 = m1.copy() - m2[0,0] *= -1 + m2[0, 0] *= -1 assert is_O3(m2) # and (M, 3, 3) n2 = np.stack((m1, m2)) @@ -82,7 +92,7 @@ def test_is_O3(): def test_is_rotation(): """Test the rotation matrix checker ``is_rotation``.""" # Normal rotation matrix - m1 = rotation_matrix(35*u.deg, 'x') + m1 = rotation_matrix(35 * u.deg, "x") assert is_rotation(m1) assert is_rotation(m1, allow_improper=True) # (a less restrictive test) # and (M, 3, 3) @@ -91,7 +101,7 @@ def test_is_rotation(): # Improper rotation (unit rotation + reflection) m2 = np.identity(3) - m2[0,0] = -1 + m2[0, 0] = -1 assert not is_rotation(m2) assert is_rotation(m2, allow_improper=True) # and (M, 3, 3) diff --git a/astropy/coordinates/tests/test_name_resolve.py b/astropy/coordinates/tests/test_name_resolve.py index ea9d121f073..7fc9580a659 100644 --- a/astropy/coordinates/tests/test_name_resolve.py +++ b/astropy/coordinates/tests/test_name_resolve.py @@ -23,7 +23,9 @@ from astropy.coordinates.sky_coordinate import SkyCoord _cached_ngc3642 = dict() -_cached_ngc3642["simbad"] = """# NGC 3642 #Q22523669 +_cached_ngc3642[ + "simbad" +] = """# NGC 3642 #Q22523669 #=S=Simbad (via url): 1 %@ 503952 %I.0 NGC 3642 @@ -38,7 +40,9 @@ #====Done (2013-Feb-12,16:37:11z)====""" -_cached_ngc3642["vizier"] = """# NGC 3642 #Q22523677 +_cached_ngc3642[ + "vizier" +] = """# NGC 3642 #Q22523677 #=V=VizieR (local): 1 %J 170.56 +59.08 = 11:22.2 +59:05 %I.0 {NGC} 3642 @@ -47,7 +51,9 @@ #====Done (2013-Feb-12,16:37:42z)====""" -_cached_ngc3642["all"] = """# ngc3642 #Q22523722 +_cached_ngc3642[ + "all" +] = """# ngc3642 #Q22523722 #=S=Simbad (via url): 1 %@ 503952 %I.0 NGC 3642 @@ -70,7 +76,9 @@ #====Done (2013-Feb-12,16:39:48z)====""" _cached_castor = dict() -_cached_castor["all"] = """# castor #Q22524249 +_cached_castor[ + "all" +] = """# castor #Q22524249 #=S=Simbad (via url): 1 %@ 983633 %I.0 NAME CASTOR @@ -92,7 +100,9 @@ #====Done (2013-Feb-12,16:52:02z)====""" -_cached_castor["simbad"] = """# castor #Q22524495 +_cached_castor[ + "simbad" +] = """# castor #Q22524495 #=S=Simbad (via url): 1 %@ 983633 %I.0 NAME CASTOR @@ -111,10 +121,16 @@ @pytest.mark.remote_data def test_names(): - # First check that sesame is up - if urllib.request.urlopen("http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame").getcode() != 200: - pytest.skip("SESAME appears to be down, skipping test_name_resolve.py:test_names()...") + if ( + urllib.request.urlopen( + "http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame" + ).getcode() + != 200 + ): + pytest.skip( + "SESAME appears to be down, skipping test_name_resolve.py:test_names()..." + ) with pytest.raises(NameResolveError): get_icrs_coordinates("m87h34hhh") @@ -123,7 +139,7 @@ def test_names(): icrs = get_icrs_coordinates("NGC 3642") except NameResolveError: ra, dec = _parse_response(_cached_ngc3642["all"]) - icrs = SkyCoord(ra=float(ra)*u.degree, dec=float(dec)*u.degree) + icrs = SkyCoord(ra=float(ra) * u.degree, dec=float(dec) * u.degree) icrs_true = SkyCoord(ra="11h 22m 18.014s", dec="59d 04m 27.27s") @@ -136,7 +152,7 @@ def test_names(): icrs = get_icrs_coordinates("castor") except NameResolveError: ra, dec = _parse_response(_cached_castor["all"]) - icrs = SkyCoord(ra=float(ra)*u.degree, dec=float(dec)*u.degree) + icrs = SkyCoord(ra=float(ra) * u.degree, dec=float(dec) * u.degree) icrs_true = SkyCoord(ra="07h 34m 35.87s", dec="+31d 53m 17.8s") np.testing.assert_almost_equal(icrs.ra.degree, icrs_true.ra.degree, 1) @@ -149,7 +165,7 @@ def test_name_resolve_cache(tmp_path): target_name = "castor" - (temp_cache_dir := tmp_path / 'cache').mkdir() + (temp_cache_dir := tmp_path / "cache").mkdir() with paths.set_temp_cache(temp_cache_dir, delete=True): assert len(get_cached_urls()) == 0 @@ -158,7 +174,9 @@ def test_name_resolve_cache(tmp_path): urls = get_cached_urls() assert len(urls) == 1 expected_urls = sesame_url.get() - assert any([urls[0].startswith(x) for x in expected_urls]), f'{urls[0]} not in {expected_urls}' + assert any( + urls[0].startswith(x) for x in expected_urls + ), f"{urls[0]} not in {expected_urls}" # Try reloading coordinates, now should just reload cached data: with no_internet(): @@ -172,29 +190,34 @@ def test_name_resolve_cache(tmp_path): def test_names_parse(): # a few test cases for parsing embedded coordinates from object name - test_names = ['CRTS SSS100805 J194428-420209', - 'MASTER OT J061451.7-272535.5', - '2MASS J06495091-0737408', - '1RXS J042555.8-194534', - 'SDSS J132411.57+032050.5', - 'DENIS-P J203137.5-000511', - '2QZ J142438.9-022739', - 'CXOU J141312.3-652013'] + test_names = [ + "CRTS SSS100805 J194428-420209", + "MASTER OT J061451.7-272535.5", + "2MASS J06495091-0737408", + "1RXS J042555.8-194534", + "SDSS J132411.57+032050.5", + "DENIS-P J203137.5-000511", + "2QZ J142438.9-022739", + "CXOU J141312.3-652013", + ] for name in test_names: sc = get_icrs_coordinates(name, parse=True) @pytest.mark.remote_data -@pytest.mark.parametrize(("name", "db_dict"), [('NGC 3642', _cached_ngc3642), - ('castor', _cached_castor)]) +@pytest.mark.parametrize( + ("name", "db_dict"), [("NGC 3642", _cached_ngc3642), ("castor", _cached_castor)] +) def test_database_specify(name, db_dict): # First check that at least some sesame mirror is up for url in sesame_url.get(): if urllib.request.urlopen(url).getcode() == 200: break else: - pytest.skip("All SESAME mirrors appear to be down, skipping " - "test_name_resolve.py:test_database_specify()...") + pytest.skip( + "All SESAME mirrors appear to be down, skipping " + "test_name_resolve.py:test_database_specify()..." + ) for db in db_dict.keys(): with sesame_database.set(db): diff --git a/astropy/coordinates/tests/test_pickle.py b/astropy/coordinates/tests/test_pickle.py index 30542d77f6e..9e4dcff63ca 100644 --- a/astropy/coordinates/tests/test_pickle.py +++ b/astropy/coordinates/tests/test_pickle.py @@ -13,13 +13,13 @@ def test_basic(): - lon1 = Longitude(1.23, "radian", wrap_angle='180d') + lon1 = Longitude(1.23, "radian", wrap_angle="180d") s = pickle.dumps(lon1) lon2 = pickle.loads(s) def test_pickle_longitude_wrap_angle(): - a = Longitude(1.23, "radian", wrap_angle='180d') + a = Longitude(1.23, "radian", wrap_angle="180d") s = pickle.dumps(a) b = pickle.loads(s) @@ -27,44 +27,42 @@ def test_pickle_longitude_wrap_angle(): assert a.wrap_angle == b.wrap_angle -_names = [coord.Angle, - coord.Distance, - coord.DynamicMatrixTransform, - coord.ICRS, - coord.Latitude, - coord.Longitude, - coord.StaticMatrixTransform, - ] - -_xfail = [False, - not HAS_SCIPY, - True, - True, - False, - True, - False] - -_args = [[0.0], - [], - [lambda *args: np.identity(3), coord.ICRS, coord.ICRS], - [0, 0], - [0], - [0], - [np.identity(3), coord.ICRS, coord.ICRS], - ] - -_kwargs = [{'unit': 'radian'}, - {'z': 0.23}, - {}, - {'unit': ['radian', 'radian']}, - {'unit': 'radian'}, - {'unit': 'radian'}, - {}, - ] - - -@pytest.mark.parametrize(("name", "args", "kwargs", "xfail"), - tuple(zip(_names, _args, _kwargs, _xfail))) +_names = [ + coord.Angle, + coord.Distance, + coord.DynamicMatrixTransform, + coord.ICRS, + coord.Latitude, + coord.Longitude, + coord.StaticMatrixTransform, +] + +_xfail = [False, not HAS_SCIPY, True, True, False, True, False] + +_args = [ + [0.0], + [], + [lambda *args: np.identity(3), coord.ICRS, coord.ICRS], + [0, 0], + [0], + [0], + [np.identity(3), coord.ICRS, coord.ICRS], +] + +_kwargs = [ + {"unit": "radian"}, + {"z": 0.23}, + {}, + {"unit": ["radian", "radian"]}, + {"unit": "radian"}, + {"unit": "radian"}, + {}, +] + + +@pytest.mark.parametrize( + ("name", "args", "kwargs", "xfail"), tuple(zip(_names, _args, _kwargs, _xfail)) +) def test_simple_object(pickle_protocol, name, args, kwargs, xfail): # noqa: F811 # Tests easily instantiated objects if xfail: @@ -80,14 +78,24 @@ class _CustomICRS(coord.ICRS): @pytest.mark.parametrize( "frame", [ - coord.SkyOffsetFrame(origin=coord.ICRS(0*u.deg, 0*u.deg)), - coord.SkyOffsetFrame(5*u.deg, 10*u.deg, origin=coord.Galactic(2*u.deg, -3*u.deg)), - coord.SkyOffsetFrame(5*u.deg, 10*u.deg, 10*u.pc, - origin=coord.Galactic(2*u.deg, -3*u.deg), - representation_type=coord.PhysicsSphericalRepresentation), - coord.SkyOffsetFrame(5*u.deg, 10*u.deg, 0*u.pc, - origin=_CustomICRS(2*u.deg, 3*u.deg, 1*u.pc)), - ] + coord.SkyOffsetFrame(origin=coord.ICRS(0 * u.deg, 0 * u.deg)), + coord.SkyOffsetFrame( + 5 * u.deg, 10 * u.deg, origin=coord.Galactic(2 * u.deg, -3 * u.deg) + ), + coord.SkyOffsetFrame( + 5 * u.deg, + 10 * u.deg, + 10 * u.pc, + origin=coord.Galactic(2 * u.deg, -3 * u.deg), + representation_type=coord.PhysicsSphericalRepresentation, + ), + coord.SkyOffsetFrame( + 5 * u.deg, + 10 * u.deg, + 0 * u.pc, + origin=_CustomICRS(2 * u.deg, 3 * u.deg, 1 * u.pc), + ), + ], ) def test_skyoffset_pickle(pickle_protocol, frame): # noqa: F811 """ diff --git a/astropy/coordinates/tests/test_regression.py b/astropy/coordinates/tests/test_regression.py index 9fbed6ffb6b..67955214f88 100644 --- a/astropy/coordinates/tests/test_regression.py +++ b/astropy/coordinates/tests/test_regression.py @@ -61,20 +61,21 @@ def test_regression_5085(): # Note: for regression test, we need to be sure that we use UTC for the # epoch, even though more properly that should be TT; but the "expected" # values were calculated using that. - j2000 = Time('J2000', scale='utc') + j2000 = Time("J2000", scale="utc") times = Time(["2015-08-28 03:30", "2015-09-05 10:30", "2015-09-15 18:35"]) - latitudes = Latitude([3.9807075, -5.00733806, 1.69539491]*u.deg) - longitudes = Longitude([311.79678613, 72.86626741, 199.58698226]*u.deg) - distances = u.Quantity([0.00243266, 0.0025424, 0.00271296]*u.au) - coo = GeocentricMeanEcliptic(lat=latitudes, - lon=longitudes, - distance=distances, obstime=times, equinox=times) + latitudes = Latitude([3.9807075, -5.00733806, 1.69539491] * u.deg) + longitudes = Longitude([311.79678613, 72.86626741, 199.58698226] * u.deg) + distances = u.Quantity([0.00243266, 0.0025424, 0.00271296] * u.au) + coo = GeocentricMeanEcliptic( + lat=latitudes, lon=longitudes, distance=distances, obstime=times, equinox=times + ) # expected result - ras = Longitude([310.50095400, 314.67109920, 319.56507428]*u.deg) - decs = Latitude([-18.25190443, -17.1556676, -15.71616522]*u.deg) - distances = u.Quantity([1.78309901, 1.710874, 1.61326649]*u.au) - expected_result = GCRS(ra=ras, dec=decs, - distance=distances, obstime=j2000).cartesian.xyz + ras = Longitude([310.50095400, 314.67109920, 319.56507428] * u.deg) + decs = Latitude([-18.25190443, -17.1556676, -15.71616522] * u.deg) + distances = u.Quantity([1.78309901, 1.710874, 1.61326649] * u.au) + expected_result = GCRS( + ra=ras, dec=decs, distance=distances, obstime=j2000 + ).cartesian.xyz actual_result = coo.transform_to(GCRS(obstime=j2000)).cartesian.xyz assert_quantity_allclose(expected_result, actual_result) @@ -83,15 +84,15 @@ def test_regression_3920(): """ Issue: https://github.com/astropy/astropy/issues/3920 """ - loc = EarthLocation.from_geodetic(0*u.deg, 0*u.deg, 0) - time = Time('2010-1-1') + loc = EarthLocation.from_geodetic(0 * u.deg, 0 * u.deg, 0) + time = Time("2010-1-1") aa = AltAz(location=loc, obstime=time) - sc = SkyCoord(10*u.deg, 3*u.deg) + sc = SkyCoord(10 * u.deg, 3 * u.deg) assert sc.transform_to(aa).shape == tuple() # That part makes sense: the input is a scalar so the output is too - sc2 = SkyCoord(10*u.deg, 3*u.deg, 1*u.AU) + sc2 = SkyCoord(10 * u.deg, 3 * u.deg, 1 * u.AU) assert sc2.transform_to(aa).shape == tuple() # in 3920 that assert fails, because the shape is (1,) @@ -109,19 +110,19 @@ def test_regression_3938(): # Set up list of targets - we don't use `from_name` here to avoid # remote_data requirements, but it does the same thing # vega = SkyCoord.from_name('Vega') - vega = SkyCoord(279.23473479*u.deg, 38.78368896*u.deg) + vega = SkyCoord(279.23473479 * u.deg, 38.78368896 * u.deg) # capella = SkyCoord.from_name('Capella') - capella = SkyCoord(79.17232794*u.deg, 45.99799147*u.deg) + capella = SkyCoord(79.17232794 * u.deg, 45.99799147 * u.deg) # sirius = SkyCoord.from_name('Sirius') - sirius = SkyCoord(101.28715533*u.deg, -16.71611586*u.deg) + sirius = SkyCoord(101.28715533 * u.deg, -16.71611586 * u.deg) targets = [vega, capella, sirius] # Feed list of targets into SkyCoord combined_coords = SkyCoord(targets) # Set up AltAz frame - time = Time('2012-01-01 00:00:00') - location = EarthLocation('10d', '45d', 0) + time = Time("2012-01-01 00:00:00") + location = EarthLocation("10d", "45d", 0) aa = AltAz(location=location, obstime=time) combined_coords.transform_to(aa) @@ -132,7 +133,7 @@ def test_regression_3998(): """ Issue: https://github.com/astropy/astropy/issues/3998 """ - time = Time('2012-01-01 00:00:00') + time = Time("2012-01-01 00:00:00") assert time.isscalar sun = get_sun(time) @@ -147,14 +148,16 @@ def test_regression_4033(): Issue: https://github.com/astropy/astropy/issues/4033 """ # alb = SkyCoord.from_name('Albireo') - alb = SkyCoord(292.68033548*u.deg, 27.95968007*u.deg) - alb_wdist = SkyCoord(alb, distance=133*u.pc) + alb = SkyCoord(292.68033548 * u.deg, 27.95968007 * u.deg) + alb_wdist = SkyCoord(alb, distance=133 * u.pc) # de = SkyCoord.from_name('Deneb') - de = SkyCoord(310.35797975*u.deg, 45.28033881*u.deg) - de_wdist = SkyCoord(de, distance=802*u.pc) + de = SkyCoord(310.35797975 * u.deg, 45.28033881 * u.deg) + de_wdist = SkyCoord(de, distance=802 * u.pc) - aa = AltAz(location=EarthLocation(lat=45*u.deg, lon=0*u.deg), obstime='2010-1-1') + aa = AltAz( + location=EarthLocation(lat=45 * u.deg, lon=0 * u.deg), obstime="2010-1-1" + ) deaa = de.transform_to(aa) albaa = alb.transform_to(aa) alb_wdistaa = alb_wdist.transform_to(aa) @@ -163,29 +166,36 @@ def test_regression_4033(): # these work fine sepnod = deaa.separation(albaa) sepwd = deaa.separation(alb_wdistaa) - assert_quantity_allclose(sepnod, 22.2862*u.deg, rtol=1e-6) - assert_quantity_allclose(sepwd, 22.2862*u.deg, rtol=1e-6) + assert_quantity_allclose(sepnod, 22.2862 * u.deg, rtol=1e-6) + assert_quantity_allclose(sepwd, 22.2862 * u.deg, rtol=1e-6) # parallax should be present when distance added - assert np.abs(sepnod - sepwd) > 1*u.marcsec + assert np.abs(sepnod - sepwd) > 1 * u.marcsec # in 4033, the following fail with a recursion error - assert_quantity_allclose(de_wdistaa.separation(alb_wdistaa), 22.2862*u.deg, rtol=1e-3) - assert_quantity_allclose(alb_wdistaa.separation(deaa), 22.2862*u.deg, rtol=1e-3) + assert_quantity_allclose( + de_wdistaa.separation(alb_wdistaa), 22.2862 * u.deg, rtol=1e-3 + ) + assert_quantity_allclose(alb_wdistaa.separation(deaa), 22.2862 * u.deg, rtol=1e-3) -@pytest.mark.skipif(not HAS_SCIPY, reason='No Scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="No Scipy") def test_regression_4082(): """ Issue: https://github.com/astropy/astropy/issues/4082 """ from astropy.coordinates import search_around_3d, search_around_sky - cat = SkyCoord([10.076, 10.00455], [18.54746, 18.54896], unit='deg') + + cat = SkyCoord([10.076, 10.00455], [18.54746, 18.54896], unit="deg") search_around_sky(cat[0:1], cat, seplimit=u.arcsec * 60, storekdtree=False) # in the issue, this raises a TypeError # also check 3d for good measure, although it's not really affected by this bug directly - cat3d = SkyCoord([10.076, 10.00455]*u.deg, [18.54746, 18.54896]*u.deg, distance=[0.1, 1.5]*u.kpc) - search_around_3d(cat3d[0:1], cat3d, 1*u.kpc, storekdtree=False) + cat3d = SkyCoord( + [10.076, 10.00455] * u.deg, + [18.54746, 18.54896] * u.deg, + distance=[0.1, 1.5] * u.kpc, + ) + search_around_3d(cat3d[0:1], cat3d, 1 * u.kpc, storekdtree=False) def test_regression_4210(): @@ -193,7 +203,7 @@ def test_regression_4210(): Issue: https://github.com/astropy/astropy/issues/4210 Related PR with actual change: https://github.com/astropy/astropy/pull/4211 """ - crd = SkyCoord(0*u.deg, 0*u.deg, distance=1*u.AU) + crd = SkyCoord(0 * u.deg, 0 * u.deg, distance=1 * u.AU) ecl = crd.geocentricmeanecliptic # bug was that "lambda", which at the time was the name of the geocentric # ecliptic longitude, is a reserved keyword. So this just makes sure the @@ -203,9 +213,10 @@ def test_regression_4210(): # and for good measure, check the other ecliptic systems are all the same # names for their attributes from astropy.coordinates.builtin_frames import ecliptic + for frame_name in ecliptic.__all__: eclcls = getattr(ecliptic, frame_name) - eclobj = eclcls(1*u.deg, 2*u.deg, 3*u.AU) + eclobj = eclcls(1 * u.deg, 2 * u.deg, 3 * u.AU) eclobj.lat eclobj.lon @@ -224,7 +235,8 @@ def test_regression_futuretimes_4302(): # appeared from astropy.coordinates.builtin_frames import utils from astropy.utils.exceptions import AstropyWarning - if hasattr(utils, '__warningregistry__'): + + if hasattr(utils, "__warningregistry__"): utils.__warningregistry__.clear() # check that out-of-range warning appears among any other warnings. If @@ -236,24 +248,25 @@ def test_regression_futuretimes_4302(): if isinstance(iers.earth_orientation_table.get(), iers.IERS_B): ctx = pytest.warns( AstropyWarning, - match=r'\(some\) times are outside of range covered by IERS table.*') + match=r"\(some\) times are outside of range covered by IERS table.*", + ) else: ctx = nullcontext() with ctx: - future_time = Time('2511-5-1') - c = CIRS(1*u.deg, 2*u.deg, obstime=future_time) + future_time = Time("2511-5-1") + c = CIRS(1 * u.deg, 2 * u.deg, obstime=future_time) c.transform_to(ITRS(obstime=future_time)) def test_regression_4996(): # this part is the actual regression test - deltat = np.linspace(-12, 12, 1000)*u.hour - times = Time('2012-7-13 00:00:00') + deltat + deltat = np.linspace(-12, 12, 1000) * u.hour + times = Time("2012-7-13 00:00:00") + deltat suncoo = get_sun(times) assert suncoo.shape == (len(times),) # and this is an additional test to make sure more complex arrays work - times2 = Time('2012-7-13 00:00:00') + deltat.reshape(10, 20, 5) + times2 = Time("2012-7-13 00:00:00") + deltat.reshape(10, 20, 5) suncoo2 = get_sun(times2) assert suncoo2.shape == times2.shape @@ -274,16 +287,17 @@ def test_regression_4293(): ra, dec = np.meshgrid(np.arange(0, 359, 45), np.arange(-80, 81, 40)) fk4 = FK4(ra.ravel() * u.deg, dec.ravel() * u.deg) - Dc = -0.065838*u.arcsec - Dd = +0.335299*u.arcsec + Dc = -0.065838 * u.arcsec + Dd = +0.335299 * u.arcsec # Dc * tan(obliquity), as given on p.170 - Dctano = -0.028553*u.arcsec + Dctano = -0.028553 * u.arcsec - fk4noe_dec = (fk4.dec - (Dd*np.cos(fk4.ra) - - Dc*np.sin(fk4.ra))*np.sin(fk4.dec) - - Dctano*np.cos(fk4.dec)) - fk4noe_ra = fk4.ra - (Dc*np.cos(fk4.ra) + - Dd*np.sin(fk4.ra)) / np.cos(fk4.dec) + fk4noe_dec = ( + fk4.dec + - (Dd * np.cos(fk4.ra) - Dc * np.sin(fk4.ra)) * np.sin(fk4.dec) + - Dctano * np.cos(fk4.dec) + ) + fk4noe_ra = fk4.ra - (Dc * np.cos(fk4.ra) + Dd * np.sin(fk4.ra)) / np.cos(fk4.dec) fk4noe = fk4.transform_to(FK4NoETerms()) # Tolerance here just set to how well the coordinates match, which is much @@ -291,13 +305,13 @@ def test_regression_4293(): # v_earth/c approximation. # Interestingly, if one divides by np.cos(fk4noe_dec) in the ra correction, # the match becomes good to 2 μas. - assert_quantity_allclose(fk4noe.ra, fk4noe_ra, atol=11.*u.uas, rtol=0) - assert_quantity_allclose(fk4noe.dec, fk4noe_dec, atol=3.*u.uas, rtol=0) + assert_quantity_allclose(fk4noe.ra, fk4noe_ra, atol=11.0 * u.uas, rtol=0) + assert_quantity_allclose(fk4noe.dec, fk4noe_dec, atol=3.0 * u.uas, rtol=0) def test_regression_4926(): - times = Time('2010-01-1') + np.arange(20)*u.day - green = get_builtin_sites()['greenwich'] + times = Time("2010-01-1") + np.arange(20) * u.day + green = get_builtin_sites()["greenwich"] # this is the regression test moon = get_moon(times, green) @@ -311,7 +325,7 @@ def test_regression_4926(): def test_regression_5209(): "check that distances are not lost on SkyCoord init" - time = Time('2015-01-01') + time = Time("2015-01-01") moon = get_moon(time) new_coord = SkyCoord([moon]) assert_quantity_allclose(new_coord[0].distance, moon.distance) @@ -322,15 +336,17 @@ def test_regression_5133(): np.random.seed(12345) lon = np.random.uniform(-10, 10, N) * u.deg lat = np.random.uniform(50, 52, N) * u.deg - alt = np.random.uniform(0, 10., N) * u.km + alt = np.random.uniform(0, 10.0, N) * u.km - time = Time('2010-1-1') + time = Time("2010-1-1") objects = EarthLocation.from_geodetic(lon, lat, height=alt) itrs_coo = objects.get_itrs(time) - homes = [EarthLocation.from_geodetic(lon=-1 * u.deg, lat=52 * u.deg, height=h) - for h in (0, 1000, 10000)*u.km] + homes = [ + EarthLocation.from_geodetic(lon=-1 * u.deg, lat=52 * u.deg, height=h) + for h in (0, 1000, 10000) * u.km + ] altaz_frames = [AltAz(obstime=time, location=h) for h in homes] altaz_coos = [itrs_coo.transform_to(f) for f in altaz_frames] @@ -354,15 +370,17 @@ def test_itrs_vals_5133(): This is worse for small height above the Earth, which is why this test uses large distances. """ - time = Time('2010-1-1') - height = 500000. * u.km - el = EarthLocation.from_geodetic(lon=20*u.deg, lat=45*u.deg, height=height) + time = Time("2010-1-1") + height = 500000.0 * u.km + el = EarthLocation.from_geodetic(lon=20 * u.deg, lat=45 * u.deg, height=height) - lons = [20, 30, 20]*u.deg - lats = [44, 45, 45]*u.deg - alts = u.Quantity([height, height, 10*height]) - coos = [EarthLocation.from_geodetic(lon, lat, height=alt).get_itrs(time) - for lon, lat, alt in zip(lons, lats, alts)] + lons = [20, 30, 20] * u.deg + lats = [44, 45, 45] * u.deg + alts = u.Quantity([height, height, 10 * height]) + coos = [ + EarthLocation.from_geodetic(lon, lat, height=alt).get_itrs(time) + for lon, lat, alt in zip(lons, lats, alts) + ] aaf = AltAz(obstime=time, location=el) aacs = [coo.transform_to(aaf) for coo in coos] @@ -370,18 +388,18 @@ def test_itrs_vals_5133(): assert all([coo.isscalar for coo in aacs]) # the ~1 degree tolerance is b/c aberration makes it not exact - assert_quantity_allclose(aacs[0].az, 180*u.deg, atol=1*u.deg) - assert aacs[0].alt < 0*u.deg - assert aacs[0].distance > 5000*u.km + assert_quantity_allclose(aacs[0].az, 180 * u.deg, atol=1 * u.deg) + assert aacs[0].alt < 0 * u.deg + assert aacs[0].distance > 5000 * u.km # it should *not* actually be 90 degrees, b/c constant latitude is not # straight east anywhere except the equator... but should be close-ish - assert_quantity_allclose(aacs[1].az, 90*u.deg, atol=5*u.deg) - assert aacs[1].alt < 0*u.deg - assert aacs[1].distance > 5000*u.km + assert_quantity_allclose(aacs[1].az, 90 * u.deg, atol=5 * u.deg) + assert aacs[1].alt < 0 * u.deg + assert aacs[1].distance > 5000 * u.km - assert_quantity_allclose(aacs[2].alt, 90*u.deg, atol=1*u.arcminute) - assert_quantity_allclose(aacs[2].distance, 9*height) + assert_quantity_allclose(aacs[2].alt, 90 * u.deg, atol=1 * u.arcminute) + assert_quantity_allclose(aacs[2].distance, 9 * height) def test_regression_simple_5133(): @@ -396,37 +414,40 @@ def test_regression_simple_5133(): This is why we construct a topocentric GCRS SkyCoord before calculating AltAz """ - t = Time('J2010') - obj = EarthLocation(-1*u.deg, 52*u.deg, height=[10., 0.]*u.km) - home = EarthLocation(-1*u.deg, 52*u.deg, height=5.*u.km) + t = Time("J2010") + obj = EarthLocation(-1 * u.deg, 52 * u.deg, height=[10.0, 0.0] * u.km) + home = EarthLocation(-1 * u.deg, 52 * u.deg, height=5.0 * u.km) obsloc_gcrs, obsvel_gcrs = home.get_gcrs_posvel(t) gcrs_geo = obj.get_itrs(t).transform_to(GCRS(obstime=t)) obsrepr = home.get_itrs(t).transform_to(GCRS(obstime=t)).cartesian topo_gcrs_repr = gcrs_geo.cartesian - obsrepr - topocentric_gcrs_frame = GCRS(obstime=t, obsgeoloc=obsloc_gcrs, obsgeovel=obsvel_gcrs) + topocentric_gcrs_frame = GCRS( + obstime=t, obsgeoloc=obsloc_gcrs, obsgeovel=obsvel_gcrs + ) gcrs_topo = topocentric_gcrs_frame.realize_frame(topo_gcrs_repr) aa = gcrs_topo.transform_to(AltAz(obstime=t, location=home)) # az is more-or-less undefined for straight up or down - assert_quantity_allclose(aa.alt, [90, -90]*u.deg, rtol=1e-7) - assert_quantity_allclose(aa.distance, 5*u.km) + assert_quantity_allclose(aa.alt, [90, -90] * u.deg, rtol=1e-7) + assert_quantity_allclose(aa.distance, 5 * u.km) def test_regression_5743(): - sc = SkyCoord([5, 10], [20, 30], unit=u.deg, - obstime=['2017-01-01T00:00', '2017-01-01T00:10']) + sc = SkyCoord( + [5, 10], [20, 30], unit=u.deg, obstime=["2017-01-01T00:00", "2017-01-01T00:10"] + ) assert sc[0].obstime.shape == tuple() def test_regression_5889_5890(): # ensure we can represent all Representations and transform to ND frames greenwich = EarthLocation( - *u.Quantity([3980608.90246817, -102.47522911, 4966861.27310067], - unit=u.m)) - times = Time("2017-03-20T12:00:00") + np.linspace(-2, 2, 3)*u.hour + *u.Quantity([3980608.90246817, -102.47522911, 4966861.27310067], unit=u.m) + ) + times = Time("2017-03-20T12:00:00") + np.linspace(-2, 2, 3) * u.hour moon = get_moon(times, location=greenwich) - targets = SkyCoord([350.7*u.deg, 260.7*u.deg], [18.4*u.deg, 22.4*u.deg]) + targets = SkyCoord([350.7 * u.deg, 260.7 * u.deg], [18.4 * u.deg, 22.4 * u.deg]) targs2d = targets[:, np.newaxis] targs2d.transform_to(moon) @@ -440,16 +461,17 @@ class MyFrame(BaseCoordinateFrame): class MySpecialFrame(MyFrame): def __init__(self, *args, **kwargs): - _rep_kwarg = kwargs.get('representation_type', None) + _rep_kwarg = kwargs.get("representation_type", None) super().__init__(*args, **kwargs) if not _rep_kwarg: self.representation_type = self.default_representation self._data = self.data.represent_as(self.representation_type) - rep1 = UnitSphericalRepresentation([0., 1]*u.deg, [2., 3.]*u.deg) - rep2 = SphericalRepresentation([10., 11]*u.deg, [12., 13.]*u.deg, - [14., 15.]*u.kpc) - mf1 = MyFrame(rep1, my_attr=1.*u.km) + rep1 = UnitSphericalRepresentation([0.0, 1] * u.deg, [2.0, 3.0] * u.deg) + rep2 = SphericalRepresentation( + [10.0, 11] * u.deg, [12.0, 13.0] * u.deg, [14.0, 15.0] * u.kpc + ) + mf1 = MyFrame(rep1, my_attr=1.0 * u.km) mf2 = mf1.realize_frame(rep2) # Normally, data is stored as is, but the representation gets set to a # default, even if a different representation instance was passed in. @@ -460,7 +482,7 @@ def __init__(self, *args, **kwargs): assert mf2.representation_type is CartesianRepresentation assert mf2.my_attr == mf1.my_attr # It should be independent of whether I set the representation explicitly - mf3 = MyFrame(rep1, my_attr=1.*u.km, representation_type='unitspherical') + mf3 = MyFrame(rep1, my_attr=1.0 * u.km, representation_type="unitspherical") mf4 = mf3.realize_frame(rep2) assert mf3.data is rep1 assert mf4.data is rep2 @@ -469,7 +491,7 @@ def __init__(self, *args, **kwargs): assert mf4.my_attr == mf3.my_attr # This should be enough to help sunpy, but just to be sure, a test # even closer to what is done there, i.e., transform the representation. - msf1 = MySpecialFrame(rep1, my_attr=1.*u.km) + msf1 = MySpecialFrame(rep1, my_attr=1.0 * u.km) msf2 = msf1.realize_frame(rep2) assert msf1.data is not rep1 # Gets transformed to Cartesian. assert msf2.data is not rep2 @@ -479,8 +501,7 @@ def __init__(self, *args, **kwargs): assert msf2.representation_type is CartesianRepresentation assert msf2.my_attr == msf1.my_attr # And finally a test where the input is not transformed. - msf3 = MySpecialFrame(rep1, my_attr=1.*u.km, - representation_type='unitspherical') + msf3 = MySpecialFrame(rep1, my_attr=1.0 * u.km, representation_type="unitspherical") msf4 = msf3.realize_frame(rep2) assert msf3.data is rep1 assert msf4.data is not rep2 @@ -489,15 +510,15 @@ def __init__(self, *args, **kwargs): assert msf4.my_attr == msf3.my_attr -@pytest.mark.skipif(not HAS_SCIPY, reason='No Scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="No Scipy") def test_regression_6347(): - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg) - sc2 = SkyCoord([1.1, 2.1]*u.deg, [3.1, 4.1]*u.deg) + sc1 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg) + sc2 = SkyCoord([1.1, 2.1] * u.deg, [3.1, 4.1] * u.deg) sc0 = sc1[:0] - idx1_10, idx2_10, d2d_10, d3d_10 = sc1.search_around_sky(sc2, 10*u.arcmin) - idx1_1, idx2_1, d2d_1, d3d_1 = sc1.search_around_sky(sc2, 1*u.arcmin) - idx1_0, idx2_0, d2d_0, d3d_0 = sc0.search_around_sky(sc2, 10*u.arcmin) + idx1_10, idx2_10, d2d_10, d3d_10 = sc1.search_around_sky(sc2, 10 * u.arcmin) + idx1_1, idx2_1, d2d_1, d3d_1 = sc1.search_around_sky(sc2, 1 * u.arcmin) + idx1_0, idx2_0, d2d_0, d3d_0 = sc0.search_around_sky(sc2, 10 * u.arcmin) assert len(d2d_10) == 2 @@ -508,15 +529,15 @@ def test_regression_6347(): assert type(d2d_1) is type(d2d_10) -@pytest.mark.skipif(not HAS_SCIPY, reason='No Scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="No Scipy") def test_regression_6347_3d(): - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, [5, 6]*u.kpc) - sc2 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, [5.1, 6.1]*u.kpc) + sc1 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, [5, 6] * u.kpc) + sc2 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, [5.1, 6.1] * u.kpc) sc0 = sc1[:0] - idx1_10, idx2_10, d2d_10, d3d_10 = sc1.search_around_3d(sc2, 500*u.pc) - idx1_1, idx2_1, d2d_1, d3d_1 = sc1.search_around_3d(sc2, 50*u.pc) - idx1_0, idx2_0, d2d_0, d3d_0 = sc0.search_around_3d(sc2, 500*u.pc) + idx1_10, idx2_10, d2d_10, d3d_10 = sc1.search_around_3d(sc2, 500 * u.pc) + idx1_1, idx2_1, d2d_1, d3d_1 = sc1.search_around_3d(sc2, 50 * u.pc) + idx1_0, idx2_0, d2d_0, d3d_0 = sc0.search_around_3d(sc2, 500 * u.pc) assert len(d2d_10) > 0 @@ -530,33 +551,35 @@ def test_regression_6347_3d(): def test_gcrs_itrs_cartesian_repr(): # issue 6436: transformation failed if coordinate representation was # Cartesian - gcrs = GCRS(CartesianRepresentation((859.07256, -4137.20368, 5295.56871), - unit='km'), representation_type='cartesian') + gcrs = GCRS( + CartesianRepresentation((859.07256, -4137.20368, 5295.56871), unit="km"), + representation_type="cartesian", + ) gcrs.transform_to(ITRS()) def test_regression_6446(): # this succeeds even before 6446: - sc1 = SkyCoord([1, 2], [3, 4], unit='deg') + sc1 = SkyCoord([1, 2], [3, 4], unit="deg") t1 = Table([sc1]) sio1 = io.StringIO() - t1.write(sio1, format='ascii.ecsv') + t1.write(sio1, format="ascii.ecsv") # but this fails due to the 6446 bug - c1 = SkyCoord(1, 3, unit='deg') - c2 = SkyCoord(2, 4, unit='deg') + c1 = SkyCoord(1, 3, unit="deg") + c2 = SkyCoord(2, 4, unit="deg") sc2 = SkyCoord([c1, c2]) t2 = Table([sc2]) sio2 = io.StringIO() - t2.write(sio2, format='ascii.ecsv') + t2.write(sio2, format="ascii.ecsv") assert sio1.getvalue() == sio2.getvalue() def test_regression_6597(): - frame_name = 'galactic' - c1 = SkyCoord(1, 3, unit='deg', frame=frame_name) - c2 = SkyCoord(2, 4, unit='deg', frame=frame_name) + frame_name = "galactic" + c1 = SkyCoord(1, 3, unit="deg", frame=frame_name) + c2 = SkyCoord(2, 4, unit="deg", frame=frame_name) sc1 = SkyCoord([c1, c2]) assert sc1.frame.name == frame_name @@ -567,9 +590,9 @@ def test_regression_6597_2(): This tests the more subtle flaw that #6597 indirectly uncovered: that even in the case that the frames are ra/dec, they still might be the wrong *kind* """ - frame = FK4(equinox='J1949') - c1 = SkyCoord(1, 3, unit='deg', frame=frame) - c2 = SkyCoord(2, 4, unit='deg', frame=frame) + frame = FK4(equinox="J1949") + c1 = SkyCoord(1, 3, unit="deg", frame=frame) + c2 = SkyCoord(2, 4, unit="deg", frame=frame) sc1 = SkyCoord([c1, c2]) assert sc1.frame.name == frame.name @@ -582,16 +605,20 @@ def test_regression_6697(): Comparison data is derived from calculation in PINT https://github.com/nanograv/PINT/blob/master/pint/erfautils.py """ - pint_vels = CartesianRepresentation(*(348.63632871, -212.31704928, -0.60154936), unit=u.m/u.s) - location = EarthLocation(*(5327448.9957829, -1718665.73869569, 3051566.90295403), unit=u.m) - t = Time(2458036.161966612, format='jd') + pint_vels = CartesianRepresentation( + 348.63632871, -212.31704928, -0.60154936, unit=u.m / u.s + ) + location = EarthLocation( + 5327448.9957829, -1718665.73869569, 3051566.90295403, unit=u.m + ) + t = Time(2458036.161966612, format="jd") obsgeopos, obsgeovel = location.get_gcrs_posvel(t) - delta = (obsgeovel-pint_vels).norm() - assert delta < 1*u.cm/u.s + delta = (obsgeovel - pint_vels).norm() + assert delta < 1 * u.cm / u.s def test_regression_8138(): - sc = SkyCoord(1*u.deg, 2*u.deg) + sc = SkyCoord(1 * u.deg, 2 * u.deg) newframe = GCRS() sc2 = sc.transform_to(newframe) assert newframe.is_equivalent_frame(sc2.frame) @@ -628,7 +655,7 @@ def test_regression_8615(): crf = CartesianRepresentation(np.array([3, 0, 4], dtype=float) * u.pc) srf = SphericalRepresentation.from_cartesian(crf) # does not error in 8615 - cr = CartesianRepresentation(np.array([3, 0, 4], dtype='f4') * u.pc) + cr = CartesianRepresentation(np.array([3, 0, 4], dtype="f4") * u.pc) sr = SphericalRepresentation.from_cartesian(cr) # errors in 8615 assert_quantity_allclose(sr.distance, 5 * u.pc) @@ -642,42 +669,52 @@ def test_regression_8924(): # A case where the representation has a 's' differential, but we try to # re-represent only with an 's2' differential rep = CartesianRepresentation(1, 2, 3, unit=u.kpc) - dif = CartesianDifferential(4, 5, 6, u.km/u.s) + dif = CartesianDifferential(4, 5, 6, u.km / u.s) rep = rep.with_differentials(dif) with pytest.raises(ValueError): - rep._re_represent_differentials(CylindricalRepresentation, - {'s2': CylindricalDifferential}) + rep._re_represent_differentials( + CylindricalRepresentation, {"s2": CylindricalDifferential} + ) def test_regression_10092(): """ Check that we still get a proper motion even for SkyCoords without distance """ - c = SkyCoord(l=10*u.degree, b=45*u.degree, - pm_l_cosb=34*u.mas/u.yr, pm_b=-117*u.mas/u.yr, - frame='galactic', - obstime=Time('1988-12-18 05:11:23.5')) + c = SkyCoord( + l=10 * u.degree, + b=45 * u.degree, + pm_l_cosb=34 * u.mas / u.yr, + pm_b=-117 * u.mas / u.yr, + frame="galactic", + obstime=Time("1988-12-18 05:11:23.5"), + ) with pytest.warns(ErfaWarning, match='ERFA function "pmsafe" yielded .*'): - # expect ErfaWarning here - newc = c.apply_space_motion(dt=10*u.year) - assert_quantity_allclose(newc.pm_l_cosb, 33.99980714*u.mas/u.yr, - atol=1.0e-5*u.mas/u.yr) + newc = c.apply_space_motion(dt=10 * u.year) + assert_quantity_allclose( + newc.pm_l_cosb, 33.99980714 * u.mas / u.yr, atol=1.0e-5 * u.mas / u.yr + ) def test_regression_10226(): # Dictionary representation of SkyCoord should contain differentials. - sc = SkyCoord([270, 280]*u.deg, [30, 35]*u.deg, [10, 11]*u.pc, - radial_velocity=[20, -20]*u.km/u.s) + sc = SkyCoord( + [270, 280] * u.deg, + [30, 35] * u.deg, + [10, 11] * u.pc, + radial_velocity=[20, -20] * u.km / u.s, + ) sc_as_dict = sc.info._represent_as_dict() - assert 'radial_velocity' in sc_as_dict + assert "radial_velocity" in sc_as_dict # But only the components that have been specified. - assert 'pm_dec' not in sc_as_dict + assert "pm_dec" not in sc_as_dict -@pytest.mark.parametrize('mjd', ( - 52000, [52000], [[52000]], [52001, 52002], [[52001], [52002]])) +@pytest.mark.parametrize( + "mjd", (52000, [52000], [[52000]], [52001, 52002], [[52001], [52002]]) +) def test_regression_10422(mjd): """ Check that we can get a GCRS for a scalar EarthLocation and a @@ -701,8 +738,9 @@ def test_regression_10291(): If light deflection from the Sun is incorrectly applied, this increases to 557 arcseconds. """ - t = Time('2012-06-06 01:29:36') - sun = get_body('sun', t) - venus = get_body('venus', t) - assert_quantity_allclose(venus.separation(sun), - 554.427*u.arcsecond, atol=0.001*u.arcsecond) + t = Time("2012-06-06 01:29:36") + sun = get_body("sun", t) + venus = get_body("venus", t) + assert_quantity_allclose( + venus.separation(sun), 554.427 * u.arcsecond, atol=0.001 * u.arcsecond + ) diff --git a/astropy/coordinates/tests/test_representation.py b/astropy/coordinates/tests/test_representation.py index 106b9dbf4a0..8d34d19e31c 100644 --- a/astropy/coordinates/tests/test_representation.py +++ b/astropy/coordinates/tests/test_representation.py @@ -37,7 +37,7 @@ # create matrices for use in testing ``.transform()`` methods matrices = { "rotation": rotation_matrix(-10, "z", u.deg), - "general": np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + "general": np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), } @@ -77,12 +77,12 @@ def representation_equal(rep1, rep2): result = True if type(rep1) is not type(rep2): return False - if getattr(rep1, '_differentials', False): + if getattr(rep1, "_differentials", False): if rep1._differentials.keys() != rep2._differentials.keys(): return False for key, diff1 in rep1._differentials.items(): result &= components_equal(diff1, rep2._differentials[key]) - elif getattr(rep2, '_differentials', False): + elif getattr(rep2, "_differentials", False): return False return result & components_equal(rep1, rep2) @@ -92,19 +92,18 @@ def representation_equal_up_to_angular_type(rep1, rep2): result = True if type(rep1) is not type(rep2): return False - if getattr(rep1, '_differentials', False): + if getattr(rep1, "_differentials", False): if rep1._differentials.keys() != rep2._differentials.keys(): return False for key, diff1 in rep1._differentials.items(): result &= components_allclose(diff1, rep2._differentials[key]) - elif getattr(rep2, '_differentials', False): + elif getattr(rep2, "_differentials", False): return False return result & components_allclose(rep1, rep2) class TestRadialRepresentation: - def test_transform(self): """Test the ``transform`` method. Only multiplication matrices pass.""" rep = RadialRepresentation(distance=10 * u.kpc) @@ -129,9 +128,8 @@ def test_transform(self): class TestSphericalRepresentation: - def test_name(self): - assert SphericalRepresentation.get_name() == 'spherical' + assert SphericalRepresentation.get_name() == "spherical" assert SphericalRepresentation.get_name() in REPRESENTATION_CLASSES def test_empty_init(self): @@ -139,10 +137,11 @@ def test_empty_init(self): s = SphericalRepresentation() def test_init_quantity(self): - - s3 = SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) - assert s3.lon == 8. * u.hourangle - assert s3.lat == 5. * u.deg + s3 = SphericalRepresentation( + lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc + ) + assert s3.lon == 8.0 * u.hourangle + assert s3.lat == 5.0 * u.deg assert s3.distance == 10 * u.kpc assert isinstance(s3.lon, Longitude) @@ -150,9 +149,10 @@ def test_init_quantity(self): assert isinstance(s3.distance, Distance) def test_init_no_mutate_input(self): - lon = -1 * u.hourangle - s = SphericalRepresentation(lon=lon, lat=-1 * u.deg, distance=1 * u.kpc, copy=True) + s = SphericalRepresentation( + lon=lon, lat=-1 * u.deg, distance=1 * u.kpc, copy=True + ) # The longitude component should be wrapped at 24 hours assert_allclose_quantity(s.lon, 23 * u.hourangle) @@ -161,43 +161,42 @@ def test_init_no_mutate_input(self): assert_allclose_quantity(lon, -1 * u.hourangle) def test_init_lonlat(self): + s2 = SphericalRepresentation( + Longitude(8, u.hour), Latitude(5, u.deg), Distance(10, u.kpc) + ) - s2 = SphericalRepresentation(Longitude(8, u.hour), - Latitude(5, u.deg), - Distance(10, u.kpc)) - - assert s2.lon == 8. * u.hourangle - assert s2.lat == 5. * u.deg - assert s2.distance == 10. * u.kpc + assert s2.lon == 8.0 * u.hourangle + assert s2.lat == 5.0 * u.deg + assert s2.distance == 10.0 * u.kpc assert isinstance(s2.lon, Longitude) assert isinstance(s2.lat, Latitude) assert isinstance(s2.distance, Distance) # also test that wrap_angle is preserved - s3 = SphericalRepresentation(Longitude(-90, u.degree, - wrap_angle=180*u.degree), - Latitude(-45, u.degree), - Distance(1., u.Rsun)) - assert s3.lon == -90. * u.degree + s3 = SphericalRepresentation( + Longitude(-90, u.degree, wrap_angle=180 * u.degree), + Latitude(-45, u.degree), + Distance(1.0, u.Rsun), + ) + assert s3.lon == -90.0 * u.degree assert s3.lon.wrap_angle == 180 * u.degree def test_init_subclass(self): class Longitude180(Longitude): - _default_wrap_angle = 180*u.degree + _default_wrap_angle = 180 * u.degree - s = SphericalRepresentation(Longitude180(-90, u.degree), - Latitude(-45, u.degree), - Distance(1., u.Rsun)) + s = SphericalRepresentation( + Longitude180(-90, u.degree), Latitude(-45, u.degree), Distance(1.0, u.Rsun) + ) assert isinstance(s.lon, Longitude180) - assert s.lon == -90. * u.degree + assert s.lon == -90.0 * u.degree assert s.lon.wrap_angle == 180 * u.degree def test_init_array(self): - - s1 = SphericalRepresentation(lon=[8, 9] * u.hourangle, - lat=[5, 6] * u.deg, - distance=[1, 2] * u.kpc) + s1 = SphericalRepresentation( + lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=[1, 2] * u.kpc + ) assert_allclose(s1.lon.degree, [120, 135]) assert_allclose(s1.lat.degree, [5, 6]) @@ -208,7 +207,6 @@ def test_init_array(self): assert isinstance(s1.distance, Distance) def test_init_array_nocopy(self): - lon = Longitude([8, 9] * u.hourangle) lat = Latitude([5, 6] * u.deg) distance = Distance([1, 2] * u.kpc) @@ -225,22 +223,23 @@ def test_init_array_nocopy(self): def test_init_float32_array(self): """Regression test against #2983""" - lon = Longitude(np.float32([1., 2.]), u.degree) - lat = Latitude(np.float32([3., 4.]), u.degree) + lon = Longitude(np.float32([1.0, 2.0]), u.degree) + lat = Latitude(np.float32([3.0, 4.0]), u.degree) s1 = UnitSphericalRepresentation(lon=lon, lat=lat, copy=False) assert s1.lon.dtype == np.float32 assert s1.lat.dtype == np.float32 - assert s1._values['lon'].dtype == np.float32 - assert s1._values['lat'].dtype == np.float32 + assert s1._values["lon"].dtype == np.float32 + assert s1._values["lat"].dtype == np.float32 def test_reprobj(self): - - s1 = SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) + s1 = SphericalRepresentation( + lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc + ) s2 = SphericalRepresentation.from_representation(s1) - assert_allclose_quantity(s2.lon, 8. * u.hourangle) - assert_allclose_quantity(s2.lat, 5. * u.deg) + assert_allclose_quantity(s2.lon, 8.0 * u.hourangle) + assert_allclose_quantity(s2.lat, 5.0 * u.deg) assert_allclose_quantity(s2.distance, 10 * u.kpc) s3 = SphericalRepresentation(s1) @@ -248,60 +247,58 @@ def test_reprobj(self): assert representation_equal(s1, s3) def test_broadcasting(self): - - s1 = SphericalRepresentation(lon=[8, 9] * u.hourangle, - lat=[5, 6] * u.deg, - distance=10 * u.kpc) + s1 = SphericalRepresentation( + lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=10 * u.kpc + ) assert_allclose_quantity(s1.lon, [120, 135] * u.degree) assert_allclose_quantity(s1.lat, [5, 6] * u.degree) assert_allclose_quantity(s1.distance, [10, 10] * u.kpc) def test_broadcasting_mismatch(self): - with pytest.raises(ValueError) as exc: - s1 = SphericalRepresentation(lon=[8, 9, 10] * u.hourangle, - lat=[5, 6] * u.deg, - distance=[1, 2] * u.kpc) - assert exc.value.args[0] == "Input parameters lon, lat, and distance cannot be broadcast" + s1 = SphericalRepresentation( + lon=[8, 9, 10] * u.hourangle, + lat=[5, 6] * u.deg, + distance=[1, 2] * u.kpc, + ) + assert ( + exc.value.args[0] + == "Input parameters lon, lat, and distance cannot be broadcast" + ) def test_broadcasting_and_nocopy(self): - - s1 = SphericalRepresentation(lon=[200] * u.deg, - lat=[0] * u.deg, - distance=[0] * u.kpc, - copy=False) + s1 = SphericalRepresentation( + lon=[200] * u.deg, lat=[0] * u.deg, distance=[0] * u.kpc, copy=False + ) # With no copying, we should be able to modify the wrap angle of the longitude component s1.lon.wrap_angle = 180 * u.deg - s2 = SphericalRepresentation(lon=[200] * u.deg, - lat=0 * u.deg, - distance=0 * u.kpc, - copy=False) + s2 = SphericalRepresentation( + lon=[200] * u.deg, lat=0 * u.deg, distance=0 * u.kpc, copy=False + ) # We should be able to modify the wrap angle of the longitude component even if other # components need to be broadcasted s2.lon.wrap_angle = 180 * u.deg def test_readonly(self): - - s1 = SphericalRepresentation(lon=8 * u.hourangle, - lat=5 * u.deg, - distance=1. * u.kpc) + s1 = SphericalRepresentation( + lon=8 * u.hourangle, lat=5 * u.deg, distance=1.0 * u.kpc + ) with pytest.raises(AttributeError): - s1.lon = 1. * u.deg + s1.lon = 1.0 * u.deg with pytest.raises(AttributeError): - s1.lat = 1. * u.deg + s1.lat = 1.0 * u.deg with pytest.raises(AttributeError): - s1.distance = 1. * u.kpc + s1.distance = 1.0 * u.kpc def test_getitem_len_iterable(self): - - s = SphericalRepresentation(lon=np.arange(10) * u.deg, - lat=-np.arange(10) * u.deg, - distance=1 * u.kpc) + s = SphericalRepresentation( + lon=np.arange(10) * u.deg, lat=-np.arange(10) * u.deg, distance=1 * u.kpc + ) s_slc = s[2:8:2] @@ -313,10 +310,7 @@ def test_getitem_len_iterable(self): assert isiterable(s) def test_getitem_len_iterable_scalar(self): - - s = SphericalRepresentation(lon=1 * u.deg, - lat=-2 * u.deg, - distance=3 * u.kpc) + s = SphericalRepresentation(lon=1 * u.deg, lat=-2 * u.deg, distance=3 * u.kpc) with pytest.raises(TypeError): s_slc = s[0] @@ -325,87 +319,102 @@ def test_getitem_len_iterable_scalar(self): assert not isiterable(s) def test_setitem(self): - s = SphericalRepresentation(lon=np.arange(5) * u.deg, - lat=-np.arange(5) * u.deg, - distance=1 * u.kpc) - s[:2] = SphericalRepresentation(lon=10.*u.deg, lat=2.*u.deg, - distance=5.*u.kpc) + s = SphericalRepresentation( + lon=np.arange(5) * u.deg, lat=-np.arange(5) * u.deg, distance=1 * u.kpc + ) + s[:2] = SphericalRepresentation( + lon=10.0 * u.deg, lat=2.0 * u.deg, distance=5.0 * u.kpc + ) assert_allclose_quantity(s.lon, [10, 10, 2, 3, 4] * u.deg) assert_allclose_quantity(s.lat, [2, 2, -2, -3, -4] * u.deg) assert_allclose_quantity(s.distance, [5, 5, 1, 1, 1] * u.kpc) def test_negative_distance(self): """Only allowed if explicitly passed on.""" - with pytest.raises(ValueError, match='allow_negative'): - SphericalRepresentation(10*u.deg, 20*u.deg, -10*u.m) + with pytest.raises(ValueError, match="allow_negative"): + SphericalRepresentation(10 * u.deg, 20 * u.deg, -10 * u.m) - s1 = SphericalRepresentation(10*u.deg, 20*u.deg, - Distance(-10*u.m, allow_negative=True)) + s1 = SphericalRepresentation( + 10 * u.deg, 20 * u.deg, Distance(-10 * u.m, allow_negative=True) + ) - assert s1.distance == -10.*u.m + assert s1.distance == -10.0 * u.m def test_nan_distance(self): - """ This is a regression test: calling represent_as() and passing in the + """This is a regression test: calling represent_as() and passing in the same class as the object shouldn't round-trip through cartesian. """ - sph = SphericalRepresentation(1*u.deg, 2*u.deg, np.nan*u.kpc) + sph = SphericalRepresentation(1 * u.deg, 2 * u.deg, np.nan * u.kpc) new_sph = sph.represent_as(SphericalRepresentation) assert_allclose_quantity(new_sph.lon, sph.lon) assert_allclose_quantity(new_sph.lat, sph.lat) - dif = SphericalCosLatDifferential(1*u.mas/u.yr, 2*u.mas/u.yr, - 3*u.km/u.s) + dif = SphericalCosLatDifferential( + 1 * u.mas / u.yr, 2 * u.mas / u.yr, 3 * u.km / u.s + ) sph = sph.with_differentials(dif) new_sph = sph.represent_as(SphericalRepresentation) assert_allclose_quantity(new_sph.lon, sph.lon) assert_allclose_quantity(new_sph.lat, sph.lat) def test_raise_on_extra_arguments(self): - with pytest.raises(TypeError, match='got multiple values'): - SphericalRepresentation(1*u.deg, 2*u.deg, 1.*u.kpc, lat=10) + with pytest.raises(TypeError, match="got multiple values"): + SphericalRepresentation(1 * u.deg, 2 * u.deg, 1.0 * u.kpc, lat=10) - with pytest.raises(TypeError, match='unexpected keyword.*parrot'): - SphericalRepresentation(1*u.deg, 2*u.deg, 1.*u.kpc, parrot=10) + with pytest.raises(TypeError, match="unexpected keyword.*parrot"): + SphericalRepresentation(1 * u.deg, 2 * u.deg, 1.0 * u.kpc, parrot=10) def test_representation_shortcuts(self): """Test that shortcuts in ``represent_as`` don't fail.""" - difs = SphericalCosLatDifferential(4*u.mas/u.yr,5*u.mas/u.yr,6*u.km/u.s) - sph = SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc, - differentials={'s': difs}) - - got = sph.represent_as(PhysicsSphericalRepresentation, - PhysicsSphericalDifferential) + difs = SphericalCosLatDifferential( + 4 * u.mas / u.yr, 5 * u.mas / u.yr, 6 * u.km / u.s + ) + sph = SphericalRepresentation( + 1 * u.deg, 2 * u.deg, 3 * u.kpc, differentials={"s": difs} + ) + + got = sph.represent_as( + PhysicsSphericalRepresentation, PhysicsSphericalDifferential + ) assert np.may_share_memory(sph.lon, got.phi) assert np.may_share_memory(sph.distance, got.r) expected = BaseRepresentation.represent_as( - sph, PhysicsSphericalRepresentation, PhysicsSphericalDifferential) + sph, PhysicsSphericalRepresentation, PhysicsSphericalDifferential + ) # equal up to angular type assert representation_equal_up_to_angular_type(got, expected) - got = sph.represent_as(UnitSphericalRepresentation, - UnitSphericalDifferential) + got = sph.represent_as(UnitSphericalRepresentation, UnitSphericalDifferential) assert np.may_share_memory(sph.lon, got.lon) assert np.may_share_memory(sph.lat, got.lat) expected = BaseRepresentation.represent_as( - sph, UnitSphericalRepresentation, UnitSphericalDifferential) + sph, UnitSphericalRepresentation, UnitSphericalDifferential + ) assert representation_equal_up_to_angular_type(got, expected) def test_transform(self): """Test ``.transform()`` on rotation and general matrices.""" # set up representation ds1 = SphericalDifferential( - d_lon=[1, 2] * u.mas / u.yr, d_lat=[3, 4] * u.mas / u.yr, - d_distance=[-5, 6] * u.km / u.s) - s1 = SphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, - distance=[5, 6] * u.kpc, differentials=ds1) + d_lon=[1, 2] * u.mas / u.yr, + d_lat=[3, 4] * u.mas / u.yr, + d_distance=[-5, 6] * u.km / u.s, + ) + s1 = SphericalRepresentation( + lon=[1, 2] * u.deg, + lat=[3, 4] * u.deg, + distance=[5, 6] * u.kpc, + differentials=ds1, + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["rotation"]) - ds2 = s2.differentials["s"] + ds2 = s2.differentials["s"] dexpected = SphericalDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2 + ) assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) assert_allclose_quantity(s2.lat, s1.lat) @@ -423,11 +432,11 @@ def test_transform(self): s3 = s1.transform(matrices["general"]) ds3 = s3.differentials["s"] - expected = (s1.represent_as(CartesianRepresentation, - CartesianDifferential) - .transform(matrices["general"]) - .represent_as(SphericalRepresentation, - SphericalDifferential)) + expected = ( + s1.represent_as(CartesianRepresentation, CartesianDifferential) + .transform(matrices["general"]) + .represent_as(SphericalRepresentation, SphericalDifferential) + ) dexpected = expected.differentials["s"] assert_allclose_quantity(s3.lon, expected.lon) @@ -441,18 +450,24 @@ def test_transform_with_NaN(self): # all over again, but with a NaN in the distance ds1 = SphericalDifferential( - d_lon=[1, 2] * u.mas / u.yr, d_lat=[3, 4] * u.mas / u.yr, - d_distance=[-5, 6] * u.km / u.s) - s1 = SphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, - distance=[5, np.nan] * u.kpc, - differentials=ds1) + d_lon=[1, 2] * u.mas / u.yr, + d_lat=[3, 4] * u.mas / u.yr, + d_distance=[-5, 6] * u.km / u.s, + ) + s1 = SphericalRepresentation( + lon=[1, 2] * u.deg, + lat=[3, 4] * u.deg, + distance=[5, np.nan] * u.kpc, + differentials=ds1, + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["rotation"]) - ds2 = s2.differentials["s"] + ds2 = s2.differentials["s"] dexpected = SphericalDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2 + ) assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) assert_allclose_quantity(s2.lat, s1.lat) @@ -470,11 +485,13 @@ def test_transform_with_NaN(self): s3 = s1.transform(matrices["general"]) ds3 = s3.differentials["s"] - thruC = (s1.represent_as(CartesianRepresentation, - CartesianDifferential) - .transform(matrices["general"]) - .represent_as(SphericalRepresentation, - differential_class=SphericalDifferential)) + thruC = ( + s1.represent_as(CartesianRepresentation, CartesianDifferential) + .transform(matrices["general"]) + .represent_as( + SphericalRepresentation, differential_class=SphericalDifferential + ) + ) dthruC = thruC.differentials["s"] # s3 should not propagate Nan. @@ -501,9 +518,8 @@ def test_transform_with_NaN(self): class TestUnitSphericalRepresentation: - def test_name(self): - assert UnitSphericalRepresentation.get_name() == 'unitspherical' + assert UnitSphericalRepresentation.get_name() == "unitspherical" assert UnitSphericalRepresentation.get_name() in REPRESENTATION_CLASSES def test_empty_init(self): @@ -511,29 +527,24 @@ def test_empty_init(self): s = UnitSphericalRepresentation() def test_init_quantity(self): - s3 = UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) - assert s3.lon == 8. * u.hourangle - assert s3.lat == 5. * u.deg + assert s3.lon == 8.0 * u.hourangle + assert s3.lat == 5.0 * u.deg assert isinstance(s3.lon, Longitude) assert isinstance(s3.lat, Latitude) def test_init_lonlat(self): + s2 = UnitSphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg)) - s2 = UnitSphericalRepresentation(Longitude(8, u.hour), - Latitude(5, u.deg)) - - assert s2.lon == 8. * u.hourangle - assert s2.lat == 5. * u.deg + assert s2.lon == 8.0 * u.hourangle + assert s2.lat == 5.0 * u.deg assert isinstance(s2.lon, Longitude) assert isinstance(s2.lat, Latitude) def test_init_array(self): - - s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, - lat=[5, 6] * u.deg) + s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg) assert_allclose(s1.lon.degree, [120, 135]) assert_allclose(s1.lat.degree, [5, 6]) @@ -542,7 +553,6 @@ def test_init_array(self): assert isinstance(s1.lat, Latitude) def test_init_array_nocopy(self): - lon = Longitude([8, 9] * u.hourangle) lat = Latitude([5, 6] * u.deg) @@ -555,48 +565,43 @@ def test_init_array_nocopy(self): assert_allclose_quantity(lat, s1.lat) def test_reprobj(self): - s1 = UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) s2 = UnitSphericalRepresentation.from_representation(s1) - assert_allclose_quantity(s2.lon, 8. * u.hourangle) - assert_allclose_quantity(s2.lat, 5. * u.deg) + assert_allclose_quantity(s2.lon, 8.0 * u.hourangle) + assert_allclose_quantity(s2.lat, 5.0 * u.deg) s3 = UnitSphericalRepresentation(s1) assert representation_equal(s3, s1) def test_broadcasting(self): - - s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, - lat=[5, 6] * u.deg) + s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg) assert_allclose_quantity(s1.lon, [120, 135] * u.degree) assert_allclose_quantity(s1.lat, [5, 6] * u.degree) def test_broadcasting_mismatch(self): - with pytest.raises(ValueError) as exc: - s1 = UnitSphericalRepresentation(lon=[8, 9, 10] * u.hourangle, - lat=[5, 6] * u.deg) + s1 = UnitSphericalRepresentation( + lon=[8, 9, 10] * u.hourangle, lat=[5, 6] * u.deg + ) assert exc.value.args[0] == "Input parameters lon and lat cannot be broadcast" def test_readonly(self): - - s1 = UnitSphericalRepresentation(lon=8 * u.hourangle, - lat=5 * u.deg) + s1 = UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) with pytest.raises(AttributeError): - s1.lon = 1. * u.deg + s1.lon = 1.0 * u.deg with pytest.raises(AttributeError): - s1.lat = 1. * u.deg + s1.lat = 1.0 * u.deg def test_getitem(self): - - s = UnitSphericalRepresentation(lon=np.arange(10) * u.deg, - lat=-np.arange(10) * u.deg) + s = UnitSphericalRepresentation( + lon=np.arange(10) * u.deg, lat=-np.arange(10) * u.deg + ) s_slc = s[2:8:2] @@ -604,9 +609,7 @@ def test_getitem(self): assert_allclose_quantity(s_slc.lat, [-2, -4, -6] * u.deg) def test_getitem_scalar(self): - - s = UnitSphericalRepresentation(lon=1 * u.deg, - lat=-2 * u.deg) + s = UnitSphericalRepresentation(lon=1 * u.deg, lat=-2 * u.deg) with pytest.raises(TypeError): s_slc = s[0] @@ -620,37 +623,43 @@ def test_representation_shortcuts(self): # We leave the test code commented out for future use. # diffs = UnitSphericalCosLatDifferential(4*u.mas/u.yr, 5*u.mas/u.yr, # 6*u.km/u.s) - sph = UnitSphericalRepresentation(1*u.deg, 2*u.deg) - # , differentials={'s': diffs} + sph = UnitSphericalRepresentation(1 * u.deg, 2 * u.deg) + # , differentials={'s': diffs} got = sph.represent_as(PhysicsSphericalRepresentation) - # , PhysicsSphericalDifferential) + # , PhysicsSphericalDifferential) assert np.may_share_memory(sph.lon, got.phi) expected = BaseRepresentation.represent_as( - sph, PhysicsSphericalRepresentation) # PhysicsSphericalDifferential + sph, PhysicsSphericalRepresentation + ) # PhysicsSphericalDifferential assert representation_equal_up_to_angular_type(got, expected) got = sph.represent_as(SphericalRepresentation) - # , SphericalDifferential) + # , SphericalDifferential) assert np.may_share_memory(sph.lon, got.lon) assert np.may_share_memory(sph.lat, got.lat) expected = BaseRepresentation.represent_as( - sph, SphericalRepresentation) # , SphericalDifferential) + sph, SphericalRepresentation + ) # , SphericalDifferential) assert representation_equal_up_to_angular_type(got, expected) def test_transform(self): """Test ``.transform()`` on rotation and general matrices.""" # set up representation - ds1 = UnitSphericalDifferential(d_lon=[1, 2] * u.mas / u.yr, - d_lat=[3, 4] * u.mas / u.yr,) - s1 = UnitSphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, - differentials=ds1) + ds1 = UnitSphericalDifferential( + d_lon=[1, 2] * u.mas / u.yr, + d_lat=[3, 4] * u.mas / u.yr, + ) + s1 = UnitSphericalRepresentation( + lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, differentials=ds1 + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["rotation"]) - ds2 = s2.differentials["s"] + ds2 = s2.differentials["s"] dexpected = UnitSphericalDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2 + ) assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) assert_allclose_quantity(s2.lat, s1.lat) @@ -666,11 +675,13 @@ def test_transform(self): s3 = s1.transform(matrices["general"]) ds3 = s3.differentials["s"] - expected = (s1.represent_as(CartesianRepresentation, - CartesianDifferential) - .transform(matrices["general"]) - .represent_as(SphericalRepresentation, - differential_class=SphericalDifferential)) + expected = ( + s1.represent_as(CartesianRepresentation, CartesianDifferential) + .transform(matrices["general"]) + .represent_as( + SphericalRepresentation, differential_class=SphericalDifferential + ) + ) dexpected = expected.differentials["s"] assert_allclose_quantity(s3.lon, expected.lon) @@ -682,9 +693,8 @@ def test_transform(self): class TestPhysicsSphericalRepresentation: - def test_name(self): - assert PhysicsSphericalRepresentation.get_name() == 'physicsspherical' + assert PhysicsSphericalRepresentation.get_name() == "physicsspherical" assert PhysicsSphericalRepresentation.get_name() in REPRESENTATION_CLASSES def test_empty_init(self): @@ -692,10 +702,11 @@ def test_empty_init(self): s = PhysicsSphericalRepresentation() def test_init_quantity(self): - - s3 = PhysicsSphericalRepresentation(phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc) - assert s3.phi == 8. * u.hourangle - assert s3.theta == 5. * u.deg + s3 = PhysicsSphericalRepresentation( + phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc + ) + assert s3.phi == 8.0 * u.hourangle + assert s3.theta == 5.0 * u.deg assert s3.r == 10 * u.kpc assert isinstance(s3.phi, Angle) @@ -703,24 +714,22 @@ def test_init_quantity(self): assert isinstance(s3.r, Distance) def test_init_phitheta(self): + s2 = PhysicsSphericalRepresentation( + Angle(8, u.hour), Angle(5, u.deg), Distance(10, u.kpc) + ) - s2 = PhysicsSphericalRepresentation(Angle(8, u.hour), - Angle(5, u.deg), - Distance(10, u.kpc)) - - assert s2.phi == 8. * u.hourangle - assert s2.theta == 5. * u.deg - assert s2.r == 10. * u.kpc + assert s2.phi == 8.0 * u.hourangle + assert s2.theta == 5.0 * u.deg + assert s2.r == 10.0 * u.kpc assert isinstance(s2.phi, Angle) assert isinstance(s2.theta, Angle) assert isinstance(s2.r, Distance) def test_init_array(self): - - s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, - theta=[5, 6] * u.deg, - r=[1, 2] * u.kpc) + s1 = PhysicsSphericalRepresentation( + phi=[8, 9] * u.hourangle, theta=[5, 6] * u.deg, r=[1, 2] * u.kpc + ) assert_allclose(s1.phi.degree, [120, 135]) assert_allclose(s1.theta.degree, [5, 6]) @@ -731,7 +740,6 @@ def test_init_array(self): assert isinstance(s1.r, Distance) def test_init_array_nocopy(self): - phi = Angle([8, 9] * u.hourangle) theta = Angle([5, 6] * u.deg) r = Distance([1, 2] * u.kpc) @@ -747,13 +755,14 @@ def test_init_array_nocopy(self): assert_allclose_quantity(r, s1.r) def test_reprobj(self): - - s1 = PhysicsSphericalRepresentation(phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc) + s1 = PhysicsSphericalRepresentation( + phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc + ) s2 = PhysicsSphericalRepresentation.from_representation(s1) - assert_allclose_quantity(s2.phi, 8. * u.hourangle) - assert_allclose_quantity(s2.theta, 5. * u.deg) + assert_allclose_quantity(s2.phi, 8.0 * u.hourangle) + assert_allclose_quantity(s2.theta, 5.0 * u.deg) assert_allclose_quantity(s2.r, 10 * u.kpc) s3 = PhysicsSphericalRepresentation(s1) @@ -761,43 +770,40 @@ def test_reprobj(self): assert representation_equal(s3, s1) def test_broadcasting(self): - - s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, - theta=[5, 6] * u.deg, - r=10 * u.kpc) + s1 = PhysicsSphericalRepresentation( + phi=[8, 9] * u.hourangle, theta=[5, 6] * u.deg, r=10 * u.kpc + ) assert_allclose_quantity(s1.phi, [120, 135] * u.degree) assert_allclose_quantity(s1.theta, [5, 6] * u.degree) assert_allclose_quantity(s1.r, [10, 10] * u.kpc) def test_broadcasting_mismatch(self): - - with pytest.raises(ValueError) as exc: - s1 = PhysicsSphericalRepresentation(phi=[8, 9, 10] * u.hourangle, - theta=[5, 6] * u.deg, - r=[1, 2] * u.kpc) - assert exc.value.args[0] == "Input parameters phi, theta, and r cannot be broadcast" + with pytest.raises( + ValueError, match="Input parameters phi, theta, and r cannot be broadcast" + ): + s1 = PhysicsSphericalRepresentation( + phi=[8, 9, 10] * u.hourangle, theta=[5, 6] * u.deg, r=[1, 2] * u.kpc + ) def test_readonly(self): - - s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, - theta=[5, 6] * u.deg, - r=[10, 20] * u.kpc) + s1 = PhysicsSphericalRepresentation( + phi=[8, 9] * u.hourangle, theta=[5, 6] * u.deg, r=[10, 20] * u.kpc + ) with pytest.raises(AttributeError): - s1.phi = 1. * u.deg + s1.phi = 1.0 * u.deg with pytest.raises(AttributeError): - s1.theta = 1. * u.deg + s1.theta = 1.0 * u.deg with pytest.raises(AttributeError): - s1.r = 1. * u.kpc + s1.r = 1.0 * u.kpc def test_getitem(self): - - s = PhysicsSphericalRepresentation(phi=np.arange(10) * u.deg, - theta=np.arange(5, 15) * u.deg, - r=1 * u.kpc) + s = PhysicsSphericalRepresentation( + phi=np.arange(10) * u.deg, theta=np.arange(5, 15) * u.deg, r=1 * u.kpc + ) s_slc = s[2:8:2] @@ -806,39 +812,40 @@ def test_getitem(self): assert_allclose_quantity(s_slc.r, [1, 1, 1] * u.kpc) def test_getitem_scalar(self): - - s = PhysicsSphericalRepresentation(phi=1 * u.deg, - theta=2 * u.deg, - r=3 * u.kpc) + s = PhysicsSphericalRepresentation(phi=1 * u.deg, theta=2 * u.deg, r=3 * u.kpc) with pytest.raises(TypeError): s_slc = s[0] def test_representation_shortcuts(self): """Test that shortcuts in ``represent_as`` don't fail.""" - difs = PhysicsSphericalDifferential(4*u.mas/u.yr,5*u.mas/u.yr,6*u.km/u.s) - sph = PhysicsSphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc, - differentials={'s': difs}) - - got = sph.represent_as(SphericalRepresentation, - SphericalDifferential) + difs = PhysicsSphericalDifferential( + 4 * u.mas / u.yr, 5 * u.mas / u.yr, 6 * u.km / u.s + ) + sph = PhysicsSphericalRepresentation( + 1 * u.deg, 2 * u.deg, 3 * u.kpc, differentials={"s": difs} + ) + + got = sph.represent_as(SphericalRepresentation, SphericalDifferential) assert np.may_share_memory(sph.phi, got.lon) assert np.may_share_memory(sph.r, got.distance) expected = BaseRepresentation.represent_as( - sph, SphericalRepresentation, SphericalDifferential) + sph, SphericalRepresentation, SphericalDifferential + ) assert representation_equal_up_to_angular_type(got, expected) - got = sph.represent_as(UnitSphericalRepresentation, - UnitSphericalDifferential) + got = sph.represent_as(UnitSphericalRepresentation, UnitSphericalDifferential) assert np.may_share_memory(sph.phi, got.lon) expected = BaseRepresentation.represent_as( - sph, UnitSphericalRepresentation, UnitSphericalDifferential) + sph, UnitSphericalRepresentation, UnitSphericalDifferential + ) assert representation_equal_up_to_angular_type(got, expected) def test_initialize_with_nan(self): # Regression test for gh-11558: initialization used to fail. - psr = PhysicsSphericalRepresentation([1., np.nan]*u.deg, [np.nan, 2.]*u.deg, - [3., np.nan]*u.m) + psr = PhysicsSphericalRepresentation( + [1.0, np.nan] * u.deg, [np.nan, 2.0] * u.deg, [3.0, np.nan] * u.m + ) assert_array_equal(np.isnan(psr.phi), [False, True]) assert_array_equal(np.isnan(psr.theta), [True, False]) assert_array_equal(np.isnan(psr.r), [False, True]) @@ -847,18 +854,24 @@ def test_transform(self): """Test ``.transform()`` on rotation and general transform matrices.""" # set up representation ds1 = PhysicsSphericalDifferential( - d_phi=[1, 2] * u.mas / u.yr, d_theta=[3, 4] * u.mas / u.yr, - d_r=[-5, 6] * u.km / u.s) + d_phi=[1, 2] * u.mas / u.yr, + d_theta=[3, 4] * u.mas / u.yr, + d_r=[-5, 6] * u.km / u.s, + ) s1 = PhysicsSphericalRepresentation( - phi=[1, 2] * u.deg, theta=[3, 4] * u.deg, r=[5, 6] * u.kpc, - differentials=ds1) + phi=[1, 2] * u.deg, + theta=[3, 4] * u.deg, + r=[5, 6] * u.kpc, + differentials=ds1, + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["rotation"]) ds2 = s2.differentials["s"] dexpected = PhysicsSphericalDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2 + ) assert_allclose_quantity(s2.phi, s1.phi + 10 * u.deg) assert_allclose_quantity(s2.theta, s1.theta) @@ -876,11 +889,11 @@ def test_transform(self): s3 = s1.transform(matrices["general"]) ds3 = s3.differentials["s"] - expected = (s1.represent_as(CartesianRepresentation, - CartesianDifferential) - .transform(matrices["general"]) - .represent_as(PhysicsSphericalRepresentation, - PhysicsSphericalDifferential)) + expected = ( + s1.represent_as(CartesianRepresentation, CartesianDifferential) + .transform(matrices["general"]) + .represent_as(PhysicsSphericalRepresentation, PhysicsSphericalDifferential) + ) dexpected = expected.differentials["s"] assert_allclose_quantity(s3.phi, expected.phi) @@ -894,18 +907,24 @@ def test_transform_with_NaN(self): # all over again, but with a NaN in the distance ds1 = PhysicsSphericalDifferential( - d_phi=[1, 2] * u.mas / u.yr, d_theta=[3, 4] * u.mas / u.yr, - d_r=[-5, 6] * u.km / u.s) + d_phi=[1, 2] * u.mas / u.yr, + d_theta=[3, 4] * u.mas / u.yr, + d_r=[-5, 6] * u.km / u.s, + ) s1 = PhysicsSphericalRepresentation( - phi=[1, 2] * u.deg, theta=[3, 4] * u.deg, r=[5, np.nan] * u.kpc, - differentials=ds1) + phi=[1, 2] * u.deg, + theta=[3, 4] * u.deg, + r=[5, np.nan] * u.kpc, + differentials=ds1, + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["rotation"]) - ds2 = s2.differentials["s"] + ds2 = s2.differentials["s"] dexpected = PhysicsSphericalDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2 + ) assert_allclose_quantity(s2.phi, s1.phi + 10 * u.deg) assert_allclose_quantity(s2.theta, s1.theta) @@ -918,11 +937,11 @@ def test_transform_with_NaN(self): s3 = s1.transform(matrices["general"]) ds3 = s3.differentials["s"] - thruC = (s1.represent_as(CartesianRepresentation, - CartesianDifferential) - .transform(matrices["general"]) - .represent_as(PhysicsSphericalRepresentation, - PhysicsSphericalDifferential)) + thruC = ( + s1.represent_as(CartesianRepresentation, CartesianDifferential) + .transform(matrices["general"]) + .represent_as(PhysicsSphericalRepresentation, PhysicsSphericalDifferential) + ) dthruC = thruC.differentials["s"] # s3 should not propagate Nan. @@ -946,9 +965,8 @@ def test_transform_with_NaN(self): class TestCartesianRepresentation: - def test_name(self): - assert CartesianRepresentation.get_name() == 'cartesian' + assert CartesianRepresentation.get_name() == "cartesian" assert CartesianRepresentation.get_name() in REPRESENTATION_CLASSES def test_empty_init(self): @@ -956,7 +974,6 @@ def test_empty_init(self): s = CartesianRepresentation() def test_init_quantity(self): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) assert s1.x.unit is u.kpc @@ -968,7 +985,6 @@ def test_init_quantity(self): assert_allclose(s1.z.value, 3) def test_init_singleunit(self): - s1 = CartesianRepresentation(x=1, y=2, z=3, unit=u.kpc) assert s1.x.unit is u.kpc @@ -980,10 +996,9 @@ def test_init_singleunit(self): assert_allclose(s1.z.value, 3) def test_init_array(self): - - s1 = CartesianRepresentation(x=[1, 2, 3] * u.pc, - y=[2, 3, 4] * u.Mpc, - z=[3, 4, 5] * u.kpc) + s1 = CartesianRepresentation( + x=[1, 2, 3] * u.pc, y=[2, 3, 4] * u.Mpc, z=[3, 4, 5] * u.kpc + ) assert s1.x.unit is u.pc assert s1.y.unit is u.Mpc @@ -994,7 +1009,6 @@ def test_init_array(self): assert_allclose(s1.z.value, [3, 4, 5]) def test_init_one_array(self): - s1 = CartesianRepresentation(x=[1, 2, 3] * u.pc) assert s1.x.unit is u.pc @@ -1005,7 +1019,7 @@ def test_init_one_array(self): assert_allclose(s1.y.value, 2) assert_allclose(s1.z.value, 3) - r = np.arange(27.).reshape(3, 3, 3) * u.kpc + r = np.arange(27.0).reshape(3, 3, 3) * u.kpc s2 = CartesianRepresentation(r, xyz_axis=0) assert s2.shape == (3, 3) assert s2.x.unit == u.kpc @@ -1036,18 +1050,19 @@ def test_init_one_array_size_fail(self): def test_init_xyz_but_more_than_one_array_fail(self): with pytest.raises(ValueError) as exc: - CartesianRepresentation(x=[1, 2, 3] * u.pc, y=[2, 3, 4] * u.pc, - z=[3, 4, 5] * u.pc, xyz_axis=0) - assert 'xyz_axis should only be set' in str(exc.value) + CartesianRepresentation( + x=[1, 2, 3] * u.pc, y=[2, 3, 4] * u.pc, z=[3, 4, 5] * u.pc, xyz_axis=0 + ) + assert "xyz_axis should only be set" in str(exc.value) def test_init_one_array_yz_fail(self): - with pytest.raises(ValueError) as exc: + with pytest.raises( + ValueError, + match="x, y, and z are required to instantiate CartesianRepresentation", + ): CartesianRepresentation(x=[1, 2, 3, 4] * u.pc, y=[1, 2] * u.pc) - assert exc.value.args[0] == ("x, y, and z are required to instantiate " - "CartesianRepresentation") def test_init_array_nocopy(self): - x = [8, 9, 10] * u.pc y = [5, 6, 7] * u.Mpc z = [2, 3, 4] * u.kpc @@ -1063,24 +1078,23 @@ def test_init_array_nocopy(self): assert_allclose_quantity(z, s1.z) def test_xyz_is_view_if_possible(self): - xyz = np.arange(1., 10.).reshape(3, 3) + xyz = np.arange(1.0, 10.0).reshape(3, 3) s1 = CartesianRepresentation(xyz, unit=u.kpc, copy=False) s1_xyz = s1.xyz - assert s1_xyz.value[0, 0] == 1. - xyz[0, 0] = 0. - assert s1.x[0] == 0. - assert s1_xyz.value[0, 0] == 0. + assert s1_xyz.value[0, 0] == 1.0 + xyz[0, 0] = 0.0 + assert s1.x[0] == 0.0 + assert s1_xyz.value[0, 0] == 0.0 # Not possible: we don't check that tuples are from the same array - xyz = np.arange(1., 10.).reshape(3, 3) + xyz = np.arange(1.0, 10.0).reshape(3, 3) s2 = CartesianRepresentation(*xyz, unit=u.kpc, copy=False) s2_xyz = s2.xyz - assert s2_xyz.value[0, 0] == 1. - xyz[0, 0] = 0. - assert s2.x[0] == 0. - assert s2_xyz.value[0, 0] == 1. + assert s2_xyz.value[0, 0] == 1.0 + xyz[0, 0] = 0.0 + assert s2.x[0] == 0.0 + assert s2_xyz.value[0, 0] == 1.0 def test_reprobj(self): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) s2 = CartesianRepresentation.from_representation(s1) @@ -1094,7 +1108,6 @@ def test_reprobj(self): assert representation_equal(s3, s1) def test_broadcasting(self): - s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=5 * u.kpc) assert s1.x.unit == u.kpc @@ -1106,26 +1119,25 @@ def test_broadcasting(self): assert_allclose(s1.z.value, [5, 5]) def test_broadcasting_mismatch(self): - with pytest.raises(ValueError) as exc: - s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6, 7] * u.kpc) + s1 = CartesianRepresentation( + x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6, 7] * u.kpc + ) assert exc.value.args[0] == "Input parameters x, y, and z cannot be broadcast" def test_readonly(self): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) with pytest.raises(AttributeError): - s1.x = 1. * u.kpc + s1.x = 1.0 * u.kpc with pytest.raises(AttributeError): - s1.y = 1. * u.kpc + s1.y = 1.0 * u.kpc with pytest.raises(AttributeError): - s1.z = 1. * u.kpc + s1.z = 1.0 * u.kpc def test_xyz(self): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) assert isinstance(s1.xyz, u.Quantity) @@ -1134,7 +1146,6 @@ def test_xyz(self): assert_allclose(s1.xyz.value, [1, 2, 3]) def test_unit_mismatch(self): - q_len = u.Quantity([1], u.km) q_nonlen = u.Quantity([1], u.kg) @@ -1151,19 +1162,19 @@ def test_unit_mismatch(self): assert exc.value.args[0] == "x, y, and z should have matching physical types" def test_unit_non_length(self): - s1 = CartesianRepresentation(x=1 * u.kg, y=2 * u.kg, z=3 * u.kg) - s2 = CartesianRepresentation(x=1 * u.km / u.s, y=2 * u.km / u.s, z=3 * u.km / u.s) + s2 = CartesianRepresentation( + x=1 * u.km / u.s, y=2 * u.km / u.s, z=3 * u.km / u.s + ) - banana = u.def_unit('banana') + banana = u.def_unit("banana") s3 = CartesianRepresentation(x=1 * banana, y=2 * banana, z=3 * banana) def test_getitem(self): - - s = CartesianRepresentation(x=np.arange(10) * u.m, - y=-np.arange(10) * u.m, - z=3 * u.km) + s = CartesianRepresentation( + x=np.arange(10) * u.m, y=-np.arange(10) * u.m, z=3 * u.km + ) s_slc = s[2:8:2] @@ -1172,28 +1183,26 @@ def test_getitem(self): assert_allclose_quantity(s_slc.z, [3, 3, 3] * u.km) def test_getitem_scalar(self): - - s = CartesianRepresentation(x=1 * u.m, - y=-2 * u.m, - z=3 * u.km) + s = CartesianRepresentation(x=1 * u.m, y=-2 * u.m, z=3 * u.km) with pytest.raises(TypeError): s_slc = s[0] def test_transform(self): - - ds1 = CartesianDifferential(d_x=[1, 2] * u.km / u.s, - d_y=[3, 4] * u.km / u.s, - d_z=[5, 6] * u.km / u.s) - s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, - z=[5, 6] * u.kpc, differentials=ds1) + ds1 = CartesianDifferential( + d_x=[1, 2] * u.km / u.s, d_y=[3, 4] * u.km / u.s, d_z=[5, 6] * u.km / u.s + ) + s1 = CartesianRepresentation( + x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6] * u.kpc, differentials=ds1 + ) # transform representation & get comparison (thru CartesianRep) s2 = s1.transform(matrices["general"]) - ds2 = s2.differentials["s"] + ds2 = s2.differentials["s"] dexpected = CartesianDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrices["general"]), base=s2) + ds1.to_cartesian(base=s1).transform(matrices["general"]), base=s2 + ) assert_allclose_quantity(ds2.d_x, dexpected.d_x) assert_allclose_quantity(ds2.d_y, dexpected.d_y) @@ -1216,9 +1225,8 @@ def test_transform(self): class TestCylindricalRepresentation: - def test_name(self): - assert CylindricalRepresentation.get_name() == 'cylindrical' + assert CylindricalRepresentation.get_name() == "cylindrical" assert CylindricalRepresentation.get_name() in REPRESENTATION_CLASSES def test_empty_init(self): @@ -1226,7 +1234,6 @@ def test_empty_init(self): s = CylindricalRepresentation() def test_init_quantity(self): - s1 = CylindricalRepresentation(rho=1 * u.kpc, phi=2 * u.deg, z=3 * u.kpc) assert s1.rho.unit is u.kpc @@ -1238,10 +1245,9 @@ def test_init_quantity(self): assert_allclose(s1.z.value, 3) def test_init_array(self): - - s1 = CylindricalRepresentation(rho=[1, 2, 3] * u.pc, - phi=[2, 3, 4] * u.deg, - z=[3, 4, 5] * u.kpc) + s1 = CylindricalRepresentation( + rho=[1, 2, 3] * u.pc, phi=[2, 3, 4] * u.deg, z=[3, 4, 5] * u.kpc + ) assert s1.rho.unit is u.pc assert s1.phi.unit is u.deg @@ -1252,7 +1258,6 @@ def test_init_array(self): assert_allclose(s1.z.value, [3, 4, 5]) def test_init_array_nocopy(self): - rho = [8, 9, 10] * u.pc phi = [5, 6, 7] * u.deg z = [2, 3, 4] * u.kpc @@ -1268,7 +1273,6 @@ def test_init_array_nocopy(self): assert_allclose_quantity(z, s1.z) def test_reprobj(self): - s1 = CylindricalRepresentation(rho=1 * u.kpc, phi=2 * u.deg, z=3 * u.kpc) s2 = CylindricalRepresentation.from_representation(s1) @@ -1282,8 +1286,9 @@ def test_reprobj(self): assert representation_equal(s3, s1) def test_broadcasting(self): - - s1 = CylindricalRepresentation(rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=5 * u.kpc) + s1 = CylindricalRepresentation( + rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=5 * u.kpc + ) assert s1.rho.unit == u.kpc assert s1.phi.unit == u.deg @@ -1294,28 +1299,26 @@ def test_broadcasting(self): assert_allclose(s1.z.value, [5, 5]) def test_broadcasting_mismatch(self): - - with pytest.raises(ValueError) as exc: - s1 = CylindricalRepresentation(rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=[5, 6, 7] * u.kpc) - assert exc.value.args[0] == "Input parameters rho, phi, and z cannot be broadcast" + with pytest.raises( + ValueError, match="Input parameters rho, phi, and z cannot be broadcast" + ): + s1 = CylindricalRepresentation( + rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=[5, 6, 7] * u.kpc + ) def test_readonly(self): - - s1 = CylindricalRepresentation(rho=1 * u.kpc, - phi=20 * u.deg, - z=3 * u.kpc) + s1 = CylindricalRepresentation(rho=1 * u.kpc, phi=20 * u.deg, z=3 * u.kpc) with pytest.raises(AttributeError): - s1.rho = 1. * u.kpc + s1.rho = 1.0 * u.kpc with pytest.raises(AttributeError): s1.phi = 20 * u.deg with pytest.raises(AttributeError): - s1.z = 1. * u.kpc + s1.z = 1.0 * u.kpc def unit_mismatch(self): - q_len = u.Quantity([1], u.kpc) q_nonlen = u.Quantity([1], u.kg) @@ -1328,10 +1331,9 @@ def unit_mismatch(self): assert exc.value.args[0] == "rho and z should have matching physical types" def test_getitem(self): - - s = CylindricalRepresentation(rho=np.arange(10) * u.pc, - phi=-np.arange(10) * u.deg, - z=1 * u.kpc) + s = CylindricalRepresentation( + rho=np.arange(10) * u.pc, phi=-np.arange(10) * u.deg, z=1 * u.kpc + ) s_slc = s[2:8:2] @@ -1340,18 +1342,15 @@ def test_getitem(self): assert_allclose_quantity(s_slc.z, [1, 1, 1] * u.kpc) def test_getitem_scalar(self): - - s = CylindricalRepresentation(rho=1 * u.pc, - phi=-2 * u.deg, - z=3 * u.kpc) + s = CylindricalRepresentation(rho=1 * u.pc, phi=-2 * u.deg, z=3 * u.kpc) with pytest.raises(TypeError): s_slc = s[0] def test_transform(self): - - s1 = CylindricalRepresentation(phi=[1, 2] * u.deg, z=[3, 4] * u.pc, - rho=[5, 6] * u.kpc) + s1 = CylindricalRepresentation( + phi=[1, 2] * u.deg, z=[3, 4] * u.pc, rho=[5, 6] * u.kpc + ) s2 = s1.transform(matrices["rotation"]) @@ -1365,8 +1364,9 @@ def test_transform(self): # now with a non rotation matrix s3 = s1.transform(matrices["general"]) - expected = (s1.to_cartesian().transform(matrices["general"]) - ).represent_as(CylindricalRepresentation) + expected = (s1.to_cartesian().transform(matrices["general"])).represent_as( + CylindricalRepresentation + ) assert_allclose_quantity(s3.phi, expected.phi) assert_allclose_quantity(s3.z, expected.z) @@ -1374,13 +1374,14 @@ def test_transform(self): class TestUnitSphericalCosLatDifferential: - @pytest.mark.parametrize("matrix", list(matrices.values())) def test_transform(self, matrix): """Test ``.transform()`` on rotation and general matrices.""" # set up representation - ds1 = UnitSphericalCosLatDifferential(d_lon_coslat=[1, 2] * u.mas / u.yr, - d_lat=[3, 4] * u.mas / u.yr,) + ds1 = UnitSphericalCosLatDifferential( + d_lon_coslat=[1, 2] * u.mas / u.yr, + d_lat=[3, 4] * u.mas / u.yr, + ) s1 = UnitSphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg) # transform representation & get comparison (thru CartesianRep) @@ -1388,17 +1389,17 @@ def test_transform(self, matrix): ds2 = ds1.transform(matrix, s1, s2) dexpected = UnitSphericalCosLatDifferential.from_cartesian( - ds1.to_cartesian(base=s1).transform(matrix), base=s2) + ds1.to_cartesian(base=s1).transform(matrix), base=s2 + ) assert_allclose_quantity(ds2.d_lon_coslat, dexpected.d_lon_coslat) assert_allclose_quantity(ds2.d_lat, dexpected.d_lat) def test_cartesian_spherical_roundtrip(): - - s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, - y=[3000., 4.] * u.pc, - z=[5., 6000.] * u.pc) + s1 = CartesianRepresentation( + x=[1, 2000.0] * u.kpc, y=[3000.0, 4.0] * u.pc, z=[5.0, 6000.0] * u.pc + ) s2 = SphericalRepresentation.from_representation(s1) @@ -1416,24 +1417,22 @@ def test_cartesian_spherical_roundtrip(): def test_cartesian_setting_with_other(): + s1 = CartesianRepresentation( + x=[1, 2000.0] * u.kpc, y=[3000.0, 4.0] * u.pc, z=[5.0, 6000.0] * u.pc + ) + s1[0] = SphericalRepresentation(0.0 * u.deg, 0.0 * u.deg, 1 * u.kpc) + assert_allclose_quantity(s1.x, [1.0, 2000.0] * u.kpc) + assert_allclose_quantity(s1.y, [0.0, 4.0] * u.pc) + assert_allclose_quantity(s1.z, [0.0, 6000.0] * u.pc) - s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, - y=[3000., 4.] * u.pc, - z=[5., 6000.] * u.pc) - s1[0] = SphericalRepresentation(0.*u.deg, 0.*u.deg, 1*u.kpc) - assert_allclose_quantity(s1.x, [1., 2000.] * u.kpc) - assert_allclose_quantity(s1.y, [0., 4.] * u.pc) - assert_allclose_quantity(s1.z, [0., 6000.] * u.pc) - - with pytest.raises(ValueError, match='loss of information'): - s1[1] = UnitSphericalRepresentation(0.*u.deg, 10.*u.deg) + with pytest.raises(ValueError, match="loss of information"): + s1[1] = UnitSphericalRepresentation(0.0 * u.deg, 10.0 * u.deg) def test_cartesian_physics_spherical_roundtrip(): - - s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, - y=[3000., 4.] * u.pc, - z=[5., 6000.] * u.pc) + s1 = CartesianRepresentation( + x=[1, 2000.0] * u.kpc, y=[3000.0, 4.0] * u.pc, z=[5.0, 6000.0] * u.pc + ) s2 = PhysicsSphericalRepresentation.from_representation(s1) @@ -1451,7 +1450,6 @@ def test_cartesian_physics_spherical_roundtrip(): def test_spherical_physics_spherical_roundtrip(): - s1 = SphericalRepresentation(lon=3 * u.deg, lat=4 * u.deg, distance=3 * u.kpc) s2 = PhysicsSphericalRepresentation.from_representation(s1) @@ -1469,15 +1467,16 @@ def test_spherical_physics_spherical_roundtrip(): assert_allclose_quantity(s2.r, s4.r) assert_allclose_quantity(s1.lon, s4.phi) - assert_allclose_quantity(s1.lat, 90. * u.deg - s4.theta) + assert_allclose_quantity(s1.lat, 90.0 * u.deg - s4.theta) assert_allclose_quantity(s1.distance, s4.r) def test_cartesian_cylindrical_roundtrip(): - - s1 = CartesianRepresentation(x=np.array([1., 2000.]) * u.kpc, - y=np.array([3000., 4.]) * u.pc, - z=np.array([5., 600.]) * u.cm) + s1 = CartesianRepresentation( + x=np.array([1.0, 2000.0]) * u.kpc, + y=np.array([3000.0, 4.0]) * u.pc, + z=np.array([5.0, 600.0]) * u.cm, + ) s2 = CylindricalRepresentation.from_representation(s1) @@ -1495,9 +1494,9 @@ def test_cartesian_cylindrical_roundtrip(): def test_unit_spherical_roundtrip(): - - s1 = UnitSphericalRepresentation(lon=[10., 30.] * u.deg, - lat=[5., 6.] * u.arcmin) + s1 = UnitSphericalRepresentation( + lon=[10.0, 30.0] * u.deg, lat=[5.0, 6.0] * u.arcmin + ) s2 = CartesianRepresentation.from_representation(s1) @@ -1510,9 +1509,9 @@ def test_unit_spherical_roundtrip(): def test_no_unnecessary_copies(): - - s1 = UnitSphericalRepresentation(lon=[10., 30.] * u.deg, - lat=[5., 6.] * u.arcmin) + s1 = UnitSphericalRepresentation( + lon=[10.0, 30.0] * u.deg, lat=[5.0, 6.0] * u.arcmin + ) s2 = s1.represent_as(UnitSphericalRepresentation) assert s2 is s1 assert np.may_share_memory(s1.lon, s2.lon) @@ -1527,57 +1526,68 @@ def test_no_unnecessary_copies(): def test_representation_repr(): r1 = SphericalRepresentation(lon=1 * u.deg, lat=2.5 * u.deg, distance=1 * u.kpc) - assert repr(r1) == ('') + assert ( + repr(r1) == "" + ) r2 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) - assert repr(r2) == ('') + assert repr(r2) == "" - r3 = CartesianRepresentation(x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc) - assert repr(r3) == ('') + r3 = CartesianRepresentation( + x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc + ) + assert ( + repr(r3) == "" + ) def test_representation_repr_multi_d(): """Regression test for #5889.""" - cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit='m') - assert repr(cr) == ( - '') + cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit="m") + assert ( + repr(cr) == "" + ) # This was broken before. - assert repr(cr.T) == ( - '') + assert ( + repr(cr.T) == "" + ) def test_representation_str(): r1 = SphericalRepresentation(lon=1 * u.deg, lat=2.5 * u.deg, distance=1 * u.kpc) - assert str(r1) == '(1., 2.5, 1.) (deg, deg, kpc)' + assert str(r1) == "(1., 2.5, 1.) (deg, deg, kpc)" r2 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) - assert str(r2) == '(1., 2., 3.) kpc' + assert str(r2) == "(1., 2., 3.) kpc" - r3 = CartesianRepresentation(x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc) - assert str(r3) == '[(1., 4., 9.), (2., 4., 10.), (3., 4., 11.)] kpc' + r3 = CartesianRepresentation( + x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc + ) + assert str(r3) == "[(1., 4., 9.), (2., 4., 10.), (3., 4., 11.)] kpc" def test_representation_str_multi_d(): """Regression test for #5889.""" - cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit='m') - assert str(cr) == ( - '[[(0., 9., 18.), (1., 10., 19.), (2., 11., 20.)],\n' - ' [(3., 12., 21.), (4., 13., 22.), (5., 14., 23.)],\n' - ' [(6., 15., 24.), (7., 16., 25.), (8., 17., 26.)]] m') + cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit="m") + assert ( + str(cr) == "[[(0., 9., 18.), (1., 10., 19.), (2., 11., 20.)],\n" + " [(3., 12., 21.), (4., 13., 22.), (5., 14., 23.)],\n" + " [(6., 15., 24.), (7., 16., 25.), (8., 17., 26.)]] m" + ) # This was broken before. - assert str(cr.T) == ( - '[[(0., 9., 18.), (3., 12., 21.), (6., 15., 24.)],\n' - ' [(1., 10., 19.), (4., 13., 22.), (7., 16., 25.)],\n' - ' [(2., 11., 20.), (5., 14., 23.), (8., 17., 26.)]] m') + assert ( + str(cr.T) == "[[(0., 9., 18.), (3., 12., 21.), (6., 15., 24.)],\n" + " [(1., 10., 19.), (4., 13., 22.), (7., 16., 25.)],\n" + " [(2., 11., 20.), (5., 14., 23.), (8., 17., 26.)]] m" + ) def test_subclass_representation(): @@ -1585,19 +1595,21 @@ def test_subclass_representation(): class Longitude180(Longitude): def __new__(cls, angle, unit=None, wrap_angle=180 * u.deg, **kwargs): - self = super().__new__(cls, angle, unit=unit, wrap_angle=wrap_angle, - **kwargs) + self = super().__new__( + cls, angle, unit=unit, wrap_angle=wrap_angle, **kwargs + ) return self class SphericalWrap180Representation(SphericalRepresentation): - attr_classes = {'lon': Longitude180, - 'lat': Latitude, - 'distance': u.Quantity} + attr_classes = {"lon": Longitude180, "lat": Latitude, "distance": u.Quantity} class ICRSWrap180(ICRS): - frame_specific_representation_info = ICRS._frame_specific_representation_info.copy() - frame_specific_representation_info[SphericalWrap180Representation] = \ - frame_specific_representation_info[SphericalRepresentation] + frame_specific_representation_info = ( + ICRS._frame_specific_representation_info.copy() + ) + frame_specific_representation_info[ + SphericalWrap180Representation + ] = frame_specific_representation_info[SphericalRepresentation] default_representation = SphericalWrap180Representation c = ICRSWrap180(ra=-1 * u.deg, dec=-2 * u.deg, distance=1 * u.m) @@ -1611,9 +1623,7 @@ def test_minimal_subclass(): # Basically to check what we document works; # see doc/coordinates/representations.rst class LogDRepresentation(BaseRepresentation): - attr_classes = {'lon': Longitude, - 'lat': Latitude, - 'logd': u.Dex} + attr_classes = {"lon": Longitude, "lat": Latitude, "logd": u.Dex} def to_cartesian(self): d = self.logd.physical @@ -1630,38 +1640,39 @@ def from_cartesian(cls, cart): lat = np.arctan2(cart.z, s) return cls(lon=lon, lat=lat, logd=u.Dex(r), copy=False) - ld1 = LogDRepresentation(90.*u.deg, 0.*u.deg, 1.*u.dex(u.kpc)) - ld2 = LogDRepresentation(lon=90.*u.deg, lat=0.*u.deg, logd=1.*u.dex(u.kpc)) + ld1 = LogDRepresentation(90.0 * u.deg, 0.0 * u.deg, 1.0 * u.dex(u.kpc)) + ld2 = LogDRepresentation(lon=90.0 * u.deg, lat=0.0 * u.deg, logd=1.0 * u.dex(u.kpc)) assert np.all(ld1.lon == ld2.lon) assert np.all(ld1.lat == ld2.lat) assert np.all(ld1.logd == ld2.logd) c = ld1.to_cartesian() - assert_allclose_quantity(c.xyz, [0., 10., 0.] * u.kpc, atol=1.*u.npc) + assert_allclose_quantity(c.xyz, [0.0, 10.0, 0.0] * u.kpc, atol=1.0 * u.npc) ld3 = LogDRepresentation.from_cartesian(c) assert np.all(ld3.lon == ld2.lon) assert np.all(ld3.lat == ld2.lat) assert np.all(ld3.logd == ld2.logd) s = ld1.represent_as(SphericalRepresentation) assert_allclose_quantity(s.lon, ld1.lon) - assert_allclose_quantity(s.distance, 10.*u.kpc) + assert_allclose_quantity(s.distance, 10.0 * u.kpc) assert_allclose_quantity(s.lat, ld1.lat) with pytest.raises(TypeError): - LogDRepresentation(0.*u.deg, 1.*u.deg) + LogDRepresentation(0.0 * u.deg, 1.0 * u.deg) with pytest.raises(TypeError): - LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), lon=1.*u.deg) + LogDRepresentation( + 0.0 * u.deg, 1.0 * u.deg, 1.0 * u.dex(u.kpc), lon=1.0 * u.deg + ) with pytest.raises(TypeError): - LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), True, False) + LogDRepresentation(0.0 * u.deg, 1.0 * u.deg, 1.0 * u.dex(u.kpc), True, False) with pytest.raises(TypeError): - LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), foo='bar') + LogDRepresentation(0.0 * u.deg, 1.0 * u.deg, 1.0 * u.dex(u.kpc), foo="bar") # if we define it a second time, even the qualnames are the same, # so we raise with pytest.raises(ValueError): + class LogDRepresentation(BaseRepresentation): - attr_classes = {'lon': Longitude, - 'lat': Latitude, - 'logr': u.Dex} + attr_classes = {"lon": Longitude, "lat": Latitude, "logr": u.Dex} def test_duplicate_warning(): @@ -1671,133 +1682,149 @@ def test_duplicate_warning(): ) with pytest.warns(DuplicateRepresentationWarning): + class UnitSphericalRepresentation(BaseRepresentation): - attr_classes = {'lon': Longitude, - 'lat': Latitude} + attr_classes = {"lon": Longitude, "lat": Latitude} - assert 'unitspherical' in DUPLICATE_REPRESENTATIONS - assert 'unitspherical' not in REPRESENTATION_CLASSES - assert 'astropy.coordinates.representation.UnitSphericalRepresentation' in REPRESENTATION_CLASSES - assert __name__ + '.test_duplicate_warning..UnitSphericalRepresentation' in REPRESENTATION_CLASSES + assert "unitspherical" in DUPLICATE_REPRESENTATIONS + assert "unitspherical" not in REPRESENTATION_CLASSES + assert ( + "astropy.coordinates.representation.UnitSphericalRepresentation" + in REPRESENTATION_CLASSES + ) + assert ( + __name__ + ".test_duplicate_warning..UnitSphericalRepresentation" + in REPRESENTATION_CLASSES + ) class TestCartesianRepresentationWithDifferential: - def test_init_differential(self): - - diff = CartesianDifferential(d_x=1 * u.km/u.s, - d_y=2 * u.km/u.s, - d_z=3 * u.km/u.s) + diff = CartesianDifferential( + d_x=1 * u.km / u.s, d_y=2 * u.km / u.s, d_z=3 * u.km / u.s + ) # Check that a single differential gets turned into a 1-item dict. - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials=diff) + s1 = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials=diff + ) assert s1.x.unit is u.kpc assert s1.y.unit is u.kpc assert s1.z.unit is u.kpc assert len(s1.differentials) == 1 - assert s1.differentials['s'] is diff + assert s1.differentials["s"] is diff # can also pass in an explicit dictionary - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials={'s': diff}) + s1 = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials={"s": diff} + ) assert len(s1.differentials) == 1 - assert s1.differentials['s'] is diff + assert s1.differentials["s"] is diff # using the wrong key will cause it to fail with pytest.raises(ValueError): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials={'1 / s2': diff}) + s1 = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials={"1 / s2": diff} + ) # make sure other kwargs are handled properly - s1 = CartesianRepresentation(x=1, y=2, z=3, - differentials=diff, copy=False, unit=u.kpc) + s1 = CartesianRepresentation( + x=1, y=2, z=3, differentials=diff, copy=False, unit=u.kpc + ) assert len(s1.differentials) == 1 - assert s1.differentials['s'] is diff + assert s1.differentials["s"] is diff with pytest.raises(TypeError): # invalid type passed to differentials - CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials='garmonbozia') + CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials="garmonbozia" + ) # And that one can add it to another representation. s1 = CartesianRepresentation( CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc), - differentials=diff) + differentials=diff, + ) assert len(s1.differentials) == 1 - assert s1.differentials['s'] is diff + assert s1.differentials["s"] is diff # make sure differentials can't accept differentials with pytest.raises(TypeError): - CartesianDifferential(d_x=1 * u.km/u.s, d_y=2 * u.km/u.s, - d_z=3 * u.km/u.s, differentials=diff) + CartesianDifferential( + d_x=1 * u.km / u.s, + d_y=2 * u.km / u.s, + d_z=3 * u.km / u.s, + differentials=diff, + ) def test_init_differential_compatible(self): # TODO: more extensive checking of this # should fail - representation and differential not compatible - diff = SphericalDifferential(d_lon=1 * u.mas/u.yr, - d_lat=2 * u.mas/u.yr, - d_distance=3 * u.km/u.s) + diff = SphericalDifferential( + d_lon=1 * u.mas / u.yr, d_lat=2 * u.mas / u.yr, d_distance=3 * u.km / u.s + ) with pytest.raises(TypeError): - CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials=diff) + CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials=diff + ) # should succeed - representation and differential are compatible - diff = SphericalCosLatDifferential(d_lon_coslat=1 * u.mas/u.yr, - d_lat=2 * u.mas/u.yr, - d_distance=3 * u.km/u.s) + diff = SphericalCosLatDifferential( + d_lon_coslat=1 * u.mas / u.yr, + d_lat=2 * u.mas / u.yr, + d_distance=3 * u.km / u.s, + ) - r1 = SphericalRepresentation(lon=15*u.deg, lat=21*u.deg, - distance=1*u.pc, - differentials=diff) + r1 = SphericalRepresentation( + lon=15 * u.deg, lat=21 * u.deg, distance=1 * u.pc, differentials=diff + ) def test_init_differential_multiple_equivalent_keys(self): - d1 = CartesianDifferential(*[1, 2, 3] * u.km/u.s) - d2 = CartesianDifferential(*[4, 5, 6] * u.km/u.s) + d1 = CartesianDifferential(*[1, 2, 3] * u.km / u.s) + d2 = CartesianDifferential(*[4, 5, 6] * u.km / u.s) # verify that the check against expected_unit validates against passing # in two different but equivalent keys with pytest.raises(ValueError): - r1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials={'s': d1, 'yr': d2}) + r1 = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials={"s": d1, "yr": d2} + ) def test_init_array_broadcasting(self): - - arr1 = np.arange(8).reshape(4, 2) * u.km/u.s + arr1 = np.arange(8).reshape(4, 2) * u.km / u.s diff = CartesianDifferential(d_x=arr1, d_y=arr1, d_z=arr1) # shapes aren't compatible arr2 = np.arange(27).reshape(3, 9) * u.kpc with pytest.raises(ValueError): - rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, - differentials=diff) + rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, differentials=diff) arr2 = np.arange(8).reshape(4, 2) * u.kpc - rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, - differentials=diff) + rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, differentials=diff) assert rep.x.unit is u.kpc assert rep.y.unit is u.kpc assert rep.z.unit is u.kpc assert len(rep.differentials) == 1 - assert rep.differentials['s'] is diff + assert rep.differentials["s"] is diff - assert rep.xyz.shape == rep.differentials['s'].d_xyz.shape + assert rep.xyz.shape == rep.differentials["s"].d_xyz.shape def test_reprobj(self): - # should succeed - representation and differential are compatible - diff = SphericalCosLatDifferential(d_lon_coslat=1 * u.mas/u.yr, - d_lat=2 * u.mas/u.yr, - d_distance=3 * u.km/u.s) + diff = SphericalCosLatDifferential( + d_lon_coslat=1 * u.mas / u.yr, + d_lat=2 * u.mas / u.yr, + d_distance=3 * u.km / u.s, + ) - r1 = SphericalRepresentation(lon=15*u.deg, lat=21*u.deg, - distance=1*u.pc, - differentials=diff) + r1 = SphericalRepresentation( + lon=15 * u.deg, lat=21 * u.deg, distance=1 * u.pc, differentials=diff + ) r2 = CartesianRepresentation.from_representation(r1) - assert r2.get_name() == 'cartesian' + assert r2.get_name() == "cartesian" assert not r2.differentials r3 = SphericalRepresentation(r1) @@ -1805,193 +1832,203 @@ def test_reprobj(self): assert representation_equal(r3, r1) def test_readonly(self): - s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) with pytest.raises(AttributeError): # attribute is not settable - s1.differentials = 'thing' + s1.differentials = "thing" def test_represent_as(self): - - diff = CartesianDifferential(d_x=1 * u.km/u.s, - d_y=2 * u.km/u.s, - d_z=3 * u.km/u.s) - rep1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials=diff) + diff = CartesianDifferential( + d_x=1 * u.km / u.s, d_y=2 * u.km / u.s, d_z=3 * u.km / u.s + ) + rep1 = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials=diff + ) # Only change the representation, drop the differential new_rep = rep1.represent_as(SphericalRepresentation) - assert new_rep.get_name() == 'spherical' + assert new_rep.get_name() == "spherical" assert not new_rep.differentials # dropped # Pass in separate classes for representation, differential - new_rep = rep1.represent_as(SphericalRepresentation, - SphericalCosLatDifferential) - assert new_rep.get_name() == 'spherical' - assert new_rep.differentials['s'].get_name() == 'sphericalcoslat' + new_rep = rep1.represent_as( + SphericalRepresentation, SphericalCosLatDifferential + ) + assert new_rep.get_name() == "spherical" + assert new_rep.differentials["s"].get_name() == "sphericalcoslat" # Pass in a dictionary for the differential classes - new_rep = rep1.represent_as(SphericalRepresentation, - {'s': SphericalCosLatDifferential}) - assert new_rep.get_name() == 'spherical' - assert new_rep.differentials['s'].get_name() == 'sphericalcoslat' + new_rep = rep1.represent_as( + SphericalRepresentation, {"s": SphericalCosLatDifferential} + ) + assert new_rep.get_name() == "spherical" + assert new_rep.differentials["s"].get_name() == "sphericalcoslat" # make sure represent_as() passes through the differentials for name in REPRESENTATION_CLASSES: - if name == 'radial': + if name == "radial": # TODO: Converting a CartesianDifferential to a # RadialDifferential fails, even on `main` continue elif name.endswith("geodetic"): # TODO: Geodetic representations do not have differentials yet continue - new_rep = rep1.represent_as(REPRESENTATION_CLASSES[name], - DIFFERENTIAL_CLASSES[name]) + new_rep = rep1.represent_as( + REPRESENTATION_CLASSES[name], DIFFERENTIAL_CLASSES[name] + ) assert new_rep.get_name() == name assert len(new_rep.differentials) == 1 - assert new_rep.differentials['s'].get_name() == name + assert new_rep.differentials["s"].get_name() == name with pytest.raises(ValueError) as excinfo: - rep1.represent_as('name') - assert 'use frame object' in str(excinfo.value) - - @pytest.mark.parametrize('sph_diff,usph_diff', [ - (SphericalDifferential, UnitSphericalDifferential), - (SphericalCosLatDifferential, UnitSphericalCosLatDifferential)]) + rep1.represent_as("name") + assert "use frame object" in str(excinfo.value) + + @pytest.mark.parametrize( + "sph_diff,usph_diff", + [ + (SphericalDifferential, UnitSphericalDifferential), + (SphericalCosLatDifferential, UnitSphericalCosLatDifferential), + ], + ) def test_represent_as_unit_spherical_with_diff(self, sph_diff, usph_diff): """Test that differential angles are correctly reduced.""" - diff = CartesianDifferential(d_x=1 * u.km/u.s, - d_y=2 * u.km/u.s, - d_z=3 * u.km/u.s) - rep = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, - differentials=diff) + diff = CartesianDifferential( + d_x=1 * u.km / u.s, d_y=2 * u.km / u.s, d_z=3 * u.km / u.s + ) + rep = CartesianRepresentation( + x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, differentials=diff + ) sph = rep.represent_as(SphericalRepresentation, sph_diff) usph = rep.represent_as(UnitSphericalRepresentation, usph_diff) assert components_equal(usph, sph.represent_as(UnitSphericalRepresentation)) - assert components_equal(usph.differentials['s'], - sph.differentials['s'].represent_as(usph_diff)) + assert components_equal( + usph.differentials["s"], sph.differentials["s"].represent_as(usph_diff) + ) # Just to be sure components_equal and the represent_as work as advertised, # a sanity check: d_lat is always defined and should be the same. - assert_array_equal(sph.differentials['s'].d_lat, - usph.differentials['s'].d_lat) + assert_array_equal(sph.differentials["s"].d_lat, usph.differentials["s"].d_lat) def test_getitem(self): - - d = CartesianDifferential(d_x=np.arange(10) * u.m/u.s, - d_y=-np.arange(10) * u.m/u.s, - d_z=1. * u.m/u.s) - s = CartesianRepresentation(x=np.arange(10) * u.m, - y=-np.arange(10) * u.m, - z=3 * u.km, - differentials=d) + d = CartesianDifferential( + d_x=np.arange(10) * u.m / u.s, + d_y=-np.arange(10) * u.m / u.s, + d_z=1.0 * u.m / u.s, + ) + s = CartesianRepresentation( + x=np.arange(10) * u.m, y=-np.arange(10) * u.m, z=3 * u.km, differentials=d + ) s_slc = s[2:8:2] - s_dif = s_slc.differentials['s'] + s_dif = s_slc.differentials["s"] assert_allclose_quantity(s_slc.x, [2, 4, 6] * u.m) assert_allclose_quantity(s_slc.y, [-2, -4, -6] * u.m) assert_allclose_quantity(s_slc.z, [3, 3, 3] * u.km) - assert_allclose_quantity(s_dif.d_x, [2, 4, 6] * u.m/u.s) - assert_allclose_quantity(s_dif.d_y, [-2, -4, -6] * u.m/u.s) - assert_allclose_quantity(s_dif.d_z, [1, 1, 1] * u.m/u.s) + assert_allclose_quantity(s_dif.d_x, [2, 4, 6] * u.m / u.s) + assert_allclose_quantity(s_dif.d_y, [-2, -4, -6] * u.m / u.s) + assert_allclose_quantity(s_dif.d_z, [1, 1, 1] * u.m / u.s) def test_setitem(self): - d = CartesianDifferential(d_x=np.arange(5) * u.m/u.s, - d_y=-np.arange(5) * u.m/u.s, - d_z=1. * u.m/u.s) - s = CartesianRepresentation(x=np.arange(5) * u.m, - y=-np.arange(5) * u.m, - z=3 * u.km, - differentials=d) + d = CartesianDifferential( + d_x=np.arange(5) * u.m / u.s, + d_y=-np.arange(5) * u.m / u.s, + d_z=1.0 * u.m / u.s, + ) + s = CartesianRepresentation( + x=np.arange(5) * u.m, y=-np.arange(5) * u.m, z=3 * u.km, differentials=d + ) s[:2] = s[2] assert_array_equal(s.x, [2, 2, 2, 3, 4] * u.m) assert_array_equal(s.y, [-2, -2, -2, -3, -4] * u.m) assert_array_equal(s.z, [3, 3, 3, 3, 3] * u.km) - assert_array_equal(s.differentials['s'].d_x, - [2, 2, 2, 3, 4] * u.m/u.s) - assert_array_equal(s.differentials['s'].d_y, - [-2, -2, -2, -3, -4] * u.m/u.s) - assert_array_equal(s.differentials['s'].d_z, - [1, 1, 1, 1, 1] * u.m/u.s) + assert_array_equal(s.differentials["s"].d_x, [2, 2, 2, 3, 4] * u.m / u.s) + assert_array_equal(s.differentials["s"].d_y, [-2, -2, -2, -3, -4] * u.m / u.s) + assert_array_equal(s.differentials["s"].d_z, [1, 1, 1, 1, 1] * u.m / u.s) - s2 = s.represent_as(SphericalRepresentation, - SphericalDifferential) + s2 = s.represent_as(SphericalRepresentation, SphericalDifferential) s[0] = s2[3] assert_allclose_quantity(s.x, [3, 2, 2, 3, 4] * u.m) assert_allclose_quantity(s.y, [-3, -2, -2, -3, -4] * u.m) assert_allclose_quantity(s.z, [3, 3, 3, 3, 3] * u.km) - assert_allclose_quantity(s.differentials['s'].d_x, - [3, 2, 2, 3, 4] * u.m/u.s) - assert_allclose_quantity(s.differentials['s'].d_y, - [-3, -2, -2, -3, -4] * u.m/u.s) - assert_allclose_quantity(s.differentials['s'].d_z, - [1, 1, 1, 1, 1] * u.m/u.s) - - s3 = CartesianRepresentation(s.xyz, differentials={ - 's': d, - 's2': CartesianDifferential(np.ones((3, 5))*u.m/u.s**2)}) - with pytest.raises(ValueError, match='same differentials'): + assert_allclose_quantity(s.differentials["s"].d_x, [3, 2, 2, 3, 4] * u.m / u.s) + assert_allclose_quantity( + s.differentials["s"].d_y, [-3, -2, -2, -3, -4] * u.m / u.s + ) + assert_allclose_quantity(s.differentials["s"].d_z, [1, 1, 1, 1, 1] * u.m / u.s) + + s3 = CartesianRepresentation( + s.xyz, + differentials={ + "s": d, + "s2": CartesianDifferential(np.ones((3, 5)) * u.m / u.s**2), + }, + ) + with pytest.raises(ValueError, match="same differentials"): s[0] = s3[2] - s4 = SphericalRepresentation(0.*u.deg, 0.*u.deg, 1.*u.kpc, - differentials=RadialDifferential( - 10*u.km/u.s)) - with pytest.raises(ValueError, match='loss of information'): + s4 = SphericalRepresentation( + 0.0 * u.deg, + 0.0 * u.deg, + 1.0 * u.kpc, + differentials=RadialDifferential(10 * u.km / u.s), + ) + with pytest.raises(ValueError, match="loss of information"): s[0] = s4 def test_transform(self): - d1 = CartesianDifferential(d_x=[1, 2] * u.km/u.s, - d_y=[3, 4] * u.km/u.s, - d_z=[5, 6] * u.km/u.s) - r1 = CartesianRepresentation(x=[1, 2] * u.kpc, - y=[3, 4] * u.kpc, - z=[5, 6] * u.kpc, - differentials=d1) + d1 = CartesianDifferential( + d_x=[1, 2] * u.km / u.s, d_y=[3, 4] * u.km / u.s, d_z=[5, 6] * u.km / u.s + ) + r1 = CartesianRepresentation( + x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6] * u.kpc, differentials=d1 + ) r2 = r1.transform(matrices["general"]) - d2 = r2.differentials['s'] - assert_allclose_quantity(d2.d_x, [22., 28]*u.km/u.s) - assert_allclose_quantity(d2.d_y, [49, 64]*u.km/u.s) - assert_allclose_quantity(d2.d_z, [76, 100.]*u.km/u.s) + d2 = r2.differentials["s"] + assert_allclose_quantity(d2.d_x, [22.0, 28] * u.km / u.s) + assert_allclose_quantity(d2.d_y, [49, 64] * u.km / u.s) + assert_allclose_quantity(d2.d_z, [76, 100.0] * u.km / u.s) def test_with_differentials(self): # make sure with_differential correctly creates a new copy with the same # differential - cr = CartesianRepresentation([1, 2, 3]*u.kpc) - diff = CartesianDifferential([.1, .2, .3]*u.km/u.s) + cr = CartesianRepresentation([1, 2, 3] * u.kpc) + diff = CartesianDifferential([0.1, 0.2, 0.3] * u.km / u.s) cr2 = cr.with_differentials(diff) assert cr.differentials != cr2.differentials - assert cr2.differentials['s'] is diff + assert cr2.differentials["s"] is diff # make sure it works even if a differential is present already - diff2 = CartesianDifferential([.1, .2, .3]*u.m/u.s) - cr3 = CartesianRepresentation([1, 2, 3]*u.kpc, differentials=diff) + diff2 = CartesianDifferential([0.1, 0.2, 0.3] * u.m / u.s) + cr3 = CartesianRepresentation([1, 2, 3] * u.kpc, differentials=diff) cr4 = cr3.with_differentials(diff2) - assert cr4.differentials['s'] != cr3.differentials['s'] - assert cr4.differentials['s'] == diff2 + assert cr4.differentials["s"] != cr3.differentials["s"] + assert cr4.differentials["s"] == diff2 # also ensure a *scalar* differential will works cr5 = cr.with_differentials(diff) assert len(cr5.differentials) == 1 - assert cr5.differentials['s'] == diff + assert cr5.differentials["s"] == diff # make sure we don't update the original representation's dict - d1 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km/u.s) - d2 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km/u.s**2) - r1 = CartesianRepresentation(*np.random.random((3, 5)), unit=u.pc, - differentials=d1) + d1 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km / u.s) + d2 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km / u.s**2) + r1 = CartesianRepresentation( + *np.random.random((3, 5)), unit=u.pc, differentials=d1 + ) r2 = r1.with_differentials(d2) - assert r1.differentials['s'] is r2.differentials['s'] - assert 's2' not in r1.differentials - assert 's2' in r2.differentials + assert r1.differentials["s"] is r2.differentials["s"] + assert "s2" not in r1.differentials + assert "s2" in r2.differentials def test_repr_with_differentials(): - diff = CartesianDifferential([.1, .2, .3]*u.km/u.s) - cr = CartesianRepresentation([1, 2, 3]*u.kpc, differentials=diff) + diff = CartesianDifferential([0.1, 0.2, 0.3] * u.km / u.s) + cr = CartesianRepresentation([1, 2, 3] * u.kpc, differentials=diff) assert "has differentials w.r.t.: 's'" in repr(cr) @@ -1999,12 +2036,13 @@ def test_to_cartesian(): """ Test that to_cartesian drops the differential. """ - sd = SphericalDifferential(d_lat=1*u.deg, d_lon=2*u.deg, d_distance=10*u.m) - sr = SphericalRepresentation(lat=1*u.deg, lon=2*u.deg, distance=10*u.m, - differentials=sd) + sd = SphericalDifferential(d_lat=1 * u.deg, d_lon=2 * u.deg, d_distance=10 * u.m) + sr = SphericalRepresentation( + lat=1 * u.deg, lon=2 * u.deg, distance=10 * u.m, differentials=sd + ) cart = sr.to_cartesian() - assert cart.get_name() == 'cartesian' + assert cart.get_name() == "cartesian" assert not cart.differentials @@ -2014,13 +2052,12 @@ def unitphysics(): This fixture is used """ had_unit = False - if hasattr(PhysicsSphericalRepresentation, '_unit_representation'): + if hasattr(PhysicsSphericalRepresentation, "_unit_representation"): orig = PhysicsSphericalRepresentation._unit_representation had_unit = True class UnitPhysicsSphericalRepresentation(BaseRepresentation): - attr_classes = {'phi': Angle, - 'theta': Angle} + attr_classes = {"phi": Angle, "theta": Angle} def __init__(self, *args, copy=True, **kwargs): super().__init__(*args, copy=copy, **kwargs) @@ -2032,10 +2069,11 @@ def __init__(self, *args, copy=True, **kwargs): # necessary because the above version of `wrap_at` has to be a copy self._phi.wrap_at(360 * u.deg, inplace=True) - if np.any(self._theta < 0.*u.deg) or np.any(self._theta > 180.*u.deg): - raise ValueError('Inclination angle(s) must be within ' - '0 deg <= angle <= 180 deg, ' - 'got {}'.format(self._theta.to(u.degree))) + if np.any(self._theta < 0.0 * u.deg) or np.any(self._theta > 180.0 * u.deg): + raise ValueError( + "Inclination angle(s) must be within 0 deg <= angle <= 180 deg, " + f"got {self._theta.to(u.degree)}" + ) @property def phi(self): @@ -2049,16 +2087,16 @@ def unit_vectors(self): sinphi, cosphi = np.sin(self.phi), np.cos(self.phi) sintheta, costheta = np.sin(self.theta), np.cos(self.theta) return { - 'phi': CartesianRepresentation(-sinphi, cosphi, 0., copy=False), - 'theta': CartesianRepresentation(costheta*cosphi, - costheta*sinphi, - -sintheta, copy=False)} + "phi": CartesianRepresentation(-sinphi, cosphi, 0.0, copy=False), + "theta": CartesianRepresentation( + costheta * cosphi, costheta * sinphi, -sintheta, copy=False + ), + } def scale_factors(self): sintheta = np.sin(self.theta) - l = np.broadcast_to(1.*u.one, self.shape, subok=True) - return {'phi', sintheta, - 'theta', l} + l = np.broadcast_to(1.0 * u.one, self.shape, subok=True) + return {"phi", sintheta, "theta", l} def to_cartesian(self): x = np.sin(self.theta) * np.cos(self.phi) @@ -2081,10 +2119,11 @@ def from_cartesian(cls, cart): return cls(phi=phi, theta=theta, copy=False) def norm(self): - return u.Quantity(np.ones(self.shape), u.dimensionless_unscaled, - copy=False) + return u.Quantity(np.ones(self.shape), u.dimensionless_unscaled, copy=False) - PhysicsSphericalRepresentation._unit_representation = UnitPhysicsSphericalRepresentation + PhysicsSphericalRepresentation._unit_representation = ( + UnitPhysicsSphericalRepresentation + ) yield UnitPhysicsSphericalRepresentation if had_unit: @@ -2097,41 +2136,41 @@ def norm(self): def test_unitphysics(unitphysics): - obj = unitphysics(phi=0*u.deg, theta=10*u.deg) - objkw = unitphysics(phi=0*u.deg, theta=10*u.deg) + obj = unitphysics(phi=0 * u.deg, theta=10 * u.deg) + objkw = unitphysics(phi=0 * u.deg, theta=10 * u.deg) assert objkw.phi == obj.phi assert objkw.theta == obj.theta asphys = obj.represent_as(PhysicsSphericalRepresentation) assert asphys.phi == obj.phi assert_allclose(asphys.theta, obj.theta) - assert_allclose_quantity(asphys.r, 1*u.dimensionless_unscaled) + assert_allclose_quantity(asphys.r, 1 * u.dimensionless_unscaled) assph = obj.represent_as(SphericalRepresentation) assert assph.lon == obj.phi - assert_allclose_quantity(assph.lat, 80*u.deg) - assert_allclose_quantity(assph.distance, 1*u.dimensionless_unscaled) + assert_allclose_quantity(assph.lat, 80 * u.deg) + assert_allclose_quantity(assph.distance, 1 * u.dimensionless_unscaled) - with pytest.raises(TypeError, match='got multiple values'): - unitphysics(1*u.deg, 2*u.deg, theta=10) + with pytest.raises(TypeError, match="got multiple values"): + unitphysics(1 * u.deg, 2 * u.deg, theta=10) - with pytest.raises(TypeError, match='unexpected keyword.*parrot'): - unitphysics(1*u.deg, 2*u.deg, parrot=10) + with pytest.raises(TypeError, match="unexpected keyword.*parrot"): + unitphysics(1 * u.deg, 2 * u.deg, parrot=10) def test_distance_warning(recwarn): - SphericalRepresentation(1*u.deg, 2*u.deg, 1*u.kpc) + SphericalRepresentation(1 * u.deg, 2 * u.deg, 1 * u.kpc) with pytest.raises(ValueError) as excinfo: - SphericalRepresentation(1*u.deg, 2*u.deg, -1*u.kpc) - assert 'Distance must be >= 0' in str(excinfo.value) + SphericalRepresentation(1 * u.deg, 2 * u.deg, -1 * u.kpc) + assert "Distance must be >= 0" in str(excinfo.value) # second check is because the "originating" ValueError says the above, # while the representation one includes the below - assert 'you must explicitly pass' in str(excinfo.value) + assert "you must explicitly pass" in str(excinfo.value) def test_dtype_preservation_in_indexing(): # Regression test for issue #8614 (fixed in #8876) - xyz = np.array([[1, 0, 0], [0.9, 0.1, 0]], dtype='f4') + xyz = np.array([[1, 0, 0], [0.9, 0.1, 0]], dtype="f4") cr = CartesianRepresentation(xyz, xyz_axis=-1, unit="km") assert cr.xyz.dtype == xyz.dtype cr0 = cr[0] @@ -2141,20 +2180,18 @@ def test_dtype_preservation_in_indexing(): class TestInfo: def setup_class(cls): - cls.rep = SphericalRepresentation([0, 1]*u.deg, [2, 3]*u.deg, - 10*u.pc) - cls.diff = SphericalDifferential([10, 20]*u.mas/u.yr, - [30, 40]*u.mas/u.yr, - [50, 60]*u.km/u.s) - cls.rep_w_diff = SphericalRepresentation(cls.rep, - differentials=cls.diff) + cls.rep = SphericalRepresentation([0, 1] * u.deg, [2, 3] * u.deg, 10 * u.pc) + cls.diff = SphericalDifferential( + [10, 20] * u.mas / u.yr, [30, 40] * u.mas / u.yr, [50, 60] * u.km / u.s + ) + cls.rep_w_diff = SphericalRepresentation(cls.rep, differentials=cls.diff) def test_info_unit(self): - assert self.rep.info.unit == 'deg, deg, pc' - assert self.diff.info.unit == 'mas / yr, mas / yr, km / s' - assert self.rep_w_diff.info.unit == 'deg, deg, pc' + assert self.rep.info.unit == "deg, deg, pc" + assert self.diff.info.unit == "mas / yr, mas / yr, km / s" + assert self.rep_w_diff.info.unit == "deg, deg, pc" - @pytest.mark.parametrize('item', ['rep', 'diff', 'rep_w_diff']) + @pytest.mark.parametrize("item", ["rep", "diff", "rep_w_diff"]) def test_roundtrip(self, item): rep_or_diff = getattr(self, item) as_dict = rep_or_diff.info._represent_as_dict() @@ -2162,13 +2199,17 @@ def test_roundtrip(self, item): assert np.all(representation_equal(new, rep_or_diff)) -@pytest.mark.parametrize('cls', - [SphericalDifferential, - SphericalCosLatDifferential, - CylindricalDifferential, - PhysicsSphericalDifferential, - UnitSphericalDifferential, - UnitSphericalCosLatDifferential]) +@pytest.mark.parametrize( + "cls", + [ + SphericalDifferential, + SphericalCosLatDifferential, + CylindricalDifferential, + PhysicsSphericalDifferential, + UnitSphericalDifferential, + UnitSphericalCosLatDifferential, + ], +) def test_differential_norm_noncartesian(cls): # The norm of a non-Cartesian differential without specifying `base` should error rep = cls(0, 0, 0) @@ -2178,5 +2219,5 @@ def test_differential_norm_noncartesian(cls): def test_differential_norm_radial(): # Unlike most non-Cartesian differentials, the norm of a radial differential does not require `base` - rep = RadialDifferential(1*u.km/u.s) - assert_allclose_quantity(rep.norm(), 1*u.km/u.s) + rep = RadialDifferential(1 * u.km / u.s) + assert_allclose_quantity(rep.norm(), 1 * u.km / u.s) diff --git a/astropy/coordinates/tests/test_representation_arithmetic.py b/astropy/coordinates/tests/test_representation_arithmetic.py index 23971b6cb22..839991f51f9 100644 --- a/astropy/coordinates/tests/test_representation_arithmetic.py +++ b/astropy/coordinates/tests/test_representation_arithmetic.py @@ -1,6 +1,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import functools import operator import numpy as np @@ -30,56 +29,55 @@ from astropy.tests.helper import assert_quantity_allclose, quantity_allclose -def assert_representation_allclose(actual, desired, rtol=1.e-7, atol=None, - **kwargs): +def assert_representation_allclose(actual, desired, rtol=1.0e-7, atol=None, **kwargs): actual_xyz = actual.to_cartesian().get_xyz(xyz_axis=-1) desired_xyz = desired.to_cartesian().get_xyz(xyz_axis=-1) - actual_xyz, desired_xyz = np.broadcast_arrays(actual_xyz, desired_xyz, - subok=True) + actual_xyz, desired_xyz = np.broadcast_arrays(actual_xyz, desired_xyz, subok=True) assert_quantity_allclose(actual_xyz, desired_xyz, rtol, atol, **kwargs) -def assert_differential_allclose(actual, desired, rtol=1.e-7, **kwargs): +def assert_differential_allclose(actual, desired, rtol=1.0e-7, **kwargs): assert actual.components == desired.components for component in actual.components: actual_c = getattr(actual, component) - atol = 1.e-10 * actual_c.unit - assert_quantity_allclose(actual_c, getattr(desired, component), - rtol, atol, **kwargs) + atol = 1.0e-10 * actual_c.unit + assert_quantity_allclose( + actual_c, getattr(desired, component), rtol, atol, **kwargs + ) def representation_equal(first, second): - return functools.reduce(np.logical_and, - (getattr(first, component) == - getattr(second, component) - for component in first.components)) + return np.all( + getattr(first, comp) == getattr(second, comp) for comp in first.components + ) -class TestArithmetic(): - +class TestArithmetic: def setup_method(self): # Choose some specific coordinates, for which ``sum`` and ``dot`` # works out nicely. self.lon = Longitude(np.arange(0, 12.1, 2), u.hourangle) self.lat = Latitude(np.arange(-90, 91, 30), u.deg) - self.distance = [5., 12., 4., 2., 4., 12., 5.] * u.kpc - self.spherical = SphericalRepresentation(self.lon, self.lat, - self.distance) - self.unit_spherical = self.spherical.represent_as( - UnitSphericalRepresentation) + self.distance = [5.0, 12.0, 4.0, 2.0, 4.0, 12.0, 5.0] * u.kpc + self.spherical = SphericalRepresentation(self.lon, self.lat, self.distance) + self.unit_spherical = self.spherical.represent_as(UnitSphericalRepresentation) self.cartesian = self.spherical.to_cartesian() def test_norm_spherical(self): norm_s = self.spherical.norm() assert isinstance(norm_s, u.Quantity) # Just to be sure, test against getting object arrays. - assert norm_s.dtype.kind == 'f' + assert norm_s.dtype.kind == "f" assert np.all(norm_s == self.distance) - @pytest.mark.parametrize('representation', - (PhysicsSphericalRepresentation, - CartesianRepresentation, - CylindricalRepresentation)) + @pytest.mark.parametrize( + "representation", + ( + PhysicsSphericalRepresentation, + CartesianRepresentation, + CylindricalRepresentation, + ), + ) def test_norm(self, representation): in_rep = self.spherical.represent_as(representation) norm_rep = in_rep.norm() @@ -89,14 +87,18 @@ def test_norm(self, representation): def test_norm_unitspherical(self): norm_rep = self.unit_spherical.norm() assert norm_rep.unit == u.dimensionless_unscaled - assert np.all(norm_rep == 1. * u.dimensionless_unscaled) - - @pytest.mark.parametrize('representation', - (SphericalRepresentation, - PhysicsSphericalRepresentation, - CartesianRepresentation, - CylindricalRepresentation, - UnitSphericalRepresentation)) + assert np.all(norm_rep == 1.0 * u.dimensionless_unscaled) + + @pytest.mark.parametrize( + "representation", + ( + SphericalRepresentation, + PhysicsSphericalRepresentation, + CartesianRepresentation, + CylindricalRepresentation, + UnitSphericalRepresentation, + ), + ) def test_neg_pos(self, representation): in_rep = self.cartesian.represent_as(representation) pos_rep = +in_rep @@ -107,63 +109,69 @@ def test_neg_pos(self, representation): assert type(neg_rep) is type(in_rep) assert np.all(neg_rep.norm() == in_rep.norm()) in_rep_xyz = in_rep.to_cartesian().xyz - assert_quantity_allclose(neg_rep.to_cartesian().xyz, - -in_rep_xyz, atol=1.e-10*in_rep_xyz.unit) + assert_quantity_allclose( + neg_rep.to_cartesian().xyz, -in_rep_xyz, atol=1.0e-10 * in_rep_xyz.unit + ) def test_mul_div_spherical(self): - s0 = self.spherical / (1. * u.Myr) + s0 = self.spherical / (1.0 * u.Myr) assert isinstance(s0, SphericalRepresentation) - assert s0.distance.dtype.kind == 'f' + assert s0.distance.dtype.kind == "f" assert np.all(s0.lon == self.spherical.lon) assert np.all(s0.lat == self.spherical.lat) - assert np.all(s0.distance == self.distance / (1. * u.Myr)) - s1 = (1./u.Myr) * self.spherical + assert np.all(s0.distance == self.distance / (1.0 * u.Myr)) + s1 = (1.0 / u.Myr) * self.spherical assert isinstance(s1, SphericalRepresentation) assert np.all(representation_equal(s1, s0)) - s2 = self.spherical * np.array([[1.], [2.]]) + s2 = self.spherical * np.array([[1.0], [2.0]]) assert isinstance(s2, SphericalRepresentation) assert s2.shape == (2, self.spherical.shape[0]) assert np.all(s2.lon == self.spherical.lon) assert np.all(s2.lat == self.spherical.lat) - assert np.all(s2.distance == - self.spherical.distance * np.array([[1.], [2.]])) - s3 = np.array([[1.], [2.]]) * self.spherical + assert np.all(s2.distance == self.spherical.distance * np.array([[1.0], [2.0]])) + s3 = np.array([[1.0], [2.0]]) * self.spherical assert isinstance(s3, SphericalRepresentation) assert np.all(representation_equal(s3, s2)) s4 = -self.spherical assert isinstance(s4, SphericalRepresentation) - assert quantity_allclose(s4.to_cartesian().xyz, - -self.spherical.to_cartesian().xyz, - atol=1e-15*self.spherical.distance.unit) + assert quantity_allclose( + s4.to_cartesian().xyz, + -self.spherical.to_cartesian().xyz, + atol=1e-15 * self.spherical.distance.unit, + ) assert np.all(s4.distance == self.spherical.distance) s5 = +self.spherical assert s5 is not self.spherical assert np.all(representation_equal(s5, self.spherical)) - @pytest.mark.parametrize('representation', - (PhysicsSphericalRepresentation, - CartesianRepresentation, - CylindricalRepresentation)) + @pytest.mark.parametrize( + "representation", + ( + PhysicsSphericalRepresentation, + CartesianRepresentation, + CylindricalRepresentation, + ), + ) def test_mul_div(self, representation): in_rep = self.spherical.represent_as(representation) - r1 = in_rep / (1. * u.Myr) + r1 = in_rep / (1.0 * u.Myr) assert isinstance(r1, representation) for component in in_rep.components: in_rep_comp = getattr(in_rep, component) r1_comp = getattr(r1, component) if in_rep_comp.unit == self.distance.unit: - assert np.all(r1_comp == in_rep_comp / (1.*u.Myr)) + assert np.all(r1_comp == in_rep_comp / (1.0 * u.Myr)) else: assert np.all(r1_comp == in_rep_comp) - r2 = np.array([[1.], [2.]]) * in_rep + r2 = np.array([[1.0], [2.0]]) * in_rep assert isinstance(r2, representation) assert r2.shape == (2, in_rep.shape[0]) - assert_quantity_allclose(r2.norm(), - self.distance * np.array([[1.], [2.]])) + assert_quantity_allclose(r2.norm(), self.distance * np.array([[1.0], [2.0]])) r3 = -in_rep - assert_representation_allclose(r3.to_cartesian(), - (in_rep * -1.).to_cartesian(), atol=1e-5*u.pc) + assert_representation_allclose( + r3.to_cartesian(), (in_rep * -1.0).to_cartesian(), atol=1e-5 * u.pc + ) with pytest.raises(TypeError): in_rep * in_rep with pytest.raises(TypeError): @@ -179,14 +187,16 @@ def test_mul_div_unit_spherical(self): assert isinstance(s2, SphericalRepresentation) assert np.all(s2.lon == self.unit_spherical.lon) assert np.all(s2.lat == self.unit_spherical.lat) - assert np.all(s2.distance == 1./u.s) + assert np.all(s2.distance == 1.0 / u.s) u3 = -self.unit_spherical assert isinstance(u3, UnitSphericalRepresentation) - assert_quantity_allclose(u3.lon, self.unit_spherical.lon + 180.*u.deg) + assert_quantity_allclose(u3.lon, self.unit_spherical.lon + 180.0 * u.deg) assert np.all(u3.lat == -self.unit_spherical.lat) - assert_quantity_allclose(u3.to_cartesian().xyz, - -self.unit_spherical.to_cartesian().xyz, - atol=1.e-10*u.dimensionless_unscaled) + assert_quantity_allclose( + u3.to_cartesian().xyz, + -self.unit_spherical.to_cartesian().xyz, + atol=1.0e-10 * u.dimensionless_unscaled, + ) u4 = +self.unit_spherical assert isinstance(u4, UnitSphericalRepresentation) assert u4 is not self.unit_spherical @@ -195,115 +205,145 @@ def test_mul_div_unit_spherical(self): def test_add_sub_cartesian(self): c1 = self.cartesian + self.cartesian assert isinstance(c1, CartesianRepresentation) - assert c1.x.dtype.kind == 'f' - assert np.all(representation_equal(c1, 2. * self.cartesian)) + assert c1.x.dtype.kind == "f" + assert np.all(representation_equal(c1, 2.0 * self.cartesian)) with pytest.raises(TypeError): - self.cartesian + 10.*u.m + self.cartesian + 10.0 * u.m with pytest.raises(u.UnitsError): self.cartesian + (self.cartesian / u.s) c2 = self.cartesian - self.cartesian assert isinstance(c2, CartesianRepresentation) - assert np.all(representation_equal( - c2, CartesianRepresentation(0.*u.m, 0.*u.m, 0.*u.m))) - c3 = self.cartesian - self.cartesian / 2. + assert np.all( + representation_equal( + c2, CartesianRepresentation(0.0 * u.m, 0.0 * u.m, 0.0 * u.m) + ) + ) + c3 = self.cartesian - self.cartesian / 2.0 assert isinstance(c3, CartesianRepresentation) - assert np.all(representation_equal(c3, self.cartesian / 2.)) + assert np.all(representation_equal(c3, self.cartesian / 2.0)) - @pytest.mark.parametrize('representation', - (PhysicsSphericalRepresentation, - SphericalRepresentation, - CylindricalRepresentation)) + @pytest.mark.parametrize( + "representation", + ( + PhysicsSphericalRepresentation, + SphericalRepresentation, + CylindricalRepresentation, + ), + ) def test_add_sub(self, representation): in_rep = self.cartesian.represent_as(representation) r1 = in_rep + in_rep assert isinstance(r1, representation) - expected = 2. * in_rep + expected = 2.0 * in_rep for component in in_rep.components: - assert_quantity_allclose(getattr(r1, component), - getattr(expected, component)) + assert_quantity_allclose( + getattr(r1, component), getattr(expected, component) + ) with pytest.raises(TypeError): - 10.*u.m + in_rep + 10.0 * u.m + in_rep with pytest.raises(u.UnitsError): in_rep + (in_rep / u.s) r2 = in_rep - in_rep assert isinstance(r2, representation) assert_representation_allclose( - r2.to_cartesian(), CartesianRepresentation(0.*u.m, 0.*u.m, 0.*u.m), - atol=1e-15*u.kpc) - r3 = in_rep - in_rep / 2. + r2.to_cartesian(), + CartesianRepresentation(0.0 * u.m, 0.0 * u.m, 0.0 * u.m), + atol=1e-15 * u.kpc, + ) + r3 = in_rep - in_rep / 2.0 assert isinstance(r3, representation) - expected = in_rep / 2. + expected = in_rep / 2.0 assert_representation_allclose(r3, expected) def test_add_sub_unit_spherical(self): s1 = self.unit_spherical + self.unit_spherical assert isinstance(s1, SphericalRepresentation) - expected = 2. * self.unit_spherical + expected = 2.0 * self.unit_spherical for component in s1.components: - assert_quantity_allclose(getattr(s1, component), - getattr(expected, component)) + assert_quantity_allclose( + getattr(s1, component), getattr(expected, component) + ) with pytest.raises(TypeError): - 10.*u.m - self.unit_spherical + 10.0 * u.m - self.unit_spherical with pytest.raises(u.UnitsError): self.unit_spherical + (self.unit_spherical / u.s) - s2 = self.unit_spherical - self.unit_spherical / 2. + s2 = self.unit_spherical - self.unit_spherical / 2.0 assert isinstance(s2, SphericalRepresentation) - expected = self.unit_spherical / 2. + expected = self.unit_spherical / 2.0 for component in s2.components: - assert_quantity_allclose(getattr(s2, component), - getattr(expected, component)) - - @pytest.mark.parametrize('representation', - (CartesianRepresentation, - PhysicsSphericalRepresentation, - SphericalRepresentation, - CylindricalRepresentation)) + assert_quantity_allclose( + getattr(s2, component), getattr(expected, component) + ) + + @pytest.mark.parametrize( + "representation", + ( + CartesianRepresentation, + PhysicsSphericalRepresentation, + SphericalRepresentation, + CylindricalRepresentation, + ), + ) def test_sum_mean(self, representation): in_rep = self.spherical.represent_as(representation) r_sum = in_rep.sum() assert isinstance(r_sum, representation) expected = SphericalRepresentation( - 90. * u.deg, 0. * u.deg, 14. * u.kpc).represent_as(representation) + 90.0 * u.deg, 0.0 * u.deg, 14.0 * u.kpc + ).represent_as(representation) for component in expected.components: exp_component = getattr(expected, component) - assert_quantity_allclose(getattr(r_sum, component), - exp_component, - atol=1e-10*exp_component.unit) + assert_quantity_allclose( + getattr(r_sum, component), + exp_component, + atol=1e-10 * exp_component.unit, + ) r_mean = in_rep.mean() assert isinstance(r_mean, representation) expected = expected / len(in_rep) for component in expected.components: exp_component = getattr(expected, component) - assert_quantity_allclose(getattr(r_mean, component), - exp_component, - atol=1e-10*exp_component.unit) + assert_quantity_allclose( + getattr(r_mean, component), + exp_component, + atol=1e-10 * exp_component.unit, + ) def test_sum_mean_unit_spherical(self): s_sum = self.unit_spherical.sum() assert isinstance(s_sum, SphericalRepresentation) expected = SphericalRepresentation( - 90. * u.deg, 0. * u.deg, 3. * u.dimensionless_unscaled) + 90.0 * u.deg, 0.0 * u.deg, 3.0 * u.dimensionless_unscaled + ) for component in expected.components: exp_component = getattr(expected, component) - assert_quantity_allclose(getattr(s_sum, component), - exp_component, - atol=1e-10*exp_component.unit) + assert_quantity_allclose( + getattr(s_sum, component), + exp_component, + atol=1e-10 * exp_component.unit, + ) s_mean = self.unit_spherical.mean() assert isinstance(s_mean, SphericalRepresentation) expected = expected / len(self.unit_spherical) for component in expected.components: exp_component = getattr(expected, component) - assert_quantity_allclose(getattr(s_mean, component), - exp_component, - atol=1e-10*exp_component.unit) - - @pytest.mark.parametrize('representation', - (CartesianRepresentation, - PhysicsSphericalRepresentation, - SphericalRepresentation, - CylindricalRepresentation)) + assert_quantity_allclose( + getattr(s_mean, component), + exp_component, + atol=1e-10 * exp_component.unit, + ) + + @pytest.mark.parametrize( + "representation", + ( + CartesianRepresentation, + PhysicsSphericalRepresentation, + SphericalRepresentation, + CylindricalRepresentation, + ), + ) def test_dot(self, representation): in_rep = self.cartesian.represent_as(representation) r_dot_r = in_rep.dot(in_rep) @@ -313,15 +353,18 @@ def test_dot(self, representation): r_dot_r_rev = in_rep.dot(in_rep[::-1]) assert isinstance(r_dot_r_rev, u.Quantity) assert r_dot_r_rev.shape == in_rep.shape - expected = [-25., -126., 2., 4., 2., -126., -25.] * u.kpc**2 + expected = [-25.0, -126.0, 2.0, 4.0, 2.0, -126.0, -25.0] * u.kpc**2 assert_quantity_allclose(r_dot_r_rev, expected) - for axis in 'xyz': - project = CartesianRepresentation(*( - (1. if axis == _axis else 0.) * u.dimensionless_unscaled - for _axis in 'xyz')) - assert_quantity_allclose(in_rep.dot(project), - getattr(self.cartesian, axis), - atol=1.*u.upc) + for axis in "xyz": + project = CartesianRepresentation( + *( + (1.0 if axis == _axis else 0.0) * u.dimensionless_unscaled + for _axis in "xyz" + ) + ) + assert_quantity_allclose( + in_rep.dot(project), getattr(self.cartesian, axis), atol=1.0 * u.upc + ) with pytest.raises(TypeError): in_rep.dot(self.cartesian.xyz) @@ -329,59 +372,66 @@ def test_dot_unit_spherical(self): u_dot_u = self.unit_spherical.dot(self.unit_spherical) assert isinstance(u_dot_u, u.Quantity) assert u_dot_u.shape == self.unit_spherical.shape - assert_quantity_allclose(u_dot_u, 1.*u.dimensionless_unscaled) + assert_quantity_allclose(u_dot_u, 1.0 * u.dimensionless_unscaled) cartesian = self.unit_spherical.to_cartesian() - for axis in 'xyz': - project = CartesianRepresentation(*( - (1. if axis == _axis else 0.) * u.dimensionless_unscaled - for _axis in 'xyz')) - assert_quantity_allclose(self.unit_spherical.dot(project), - getattr(cartesian, axis), atol=1.e-10) - - @pytest.mark.parametrize('representation', - (CartesianRepresentation, - PhysicsSphericalRepresentation, - SphericalRepresentation, - CylindricalRepresentation)) + for axis in "xyz": + project = CartesianRepresentation( + *( + (1.0 if axis == _axis else 0.0) * u.dimensionless_unscaled + for _axis in "xyz" + ) + ) + assert_quantity_allclose( + self.unit_spherical.dot(project), getattr(cartesian, axis), atol=1.0e-10 + ) + + @pytest.mark.parametrize( + "representation", + ( + CartesianRepresentation, + PhysicsSphericalRepresentation, + SphericalRepresentation, + CylindricalRepresentation, + ), + ) def test_cross(self, representation): in_rep = self.cartesian.represent_as(representation) r_cross_r = in_rep.cross(in_rep) assert isinstance(r_cross_r, representation) - assert_quantity_allclose(r_cross_r.norm(), 0.*u.kpc**2, - atol=1.*u.mpc**2) + assert_quantity_allclose( + r_cross_r.norm(), 0.0 * u.kpc**2, atol=1.0 * u.mpc**2 + ) r_cross_r_rev = in_rep.cross(in_rep[::-1]) - sep = angular_separation(self.lon, self.lat, - self.lon[::-1], self.lat[::-1]) + sep = angular_separation(self.lon, self.lat, self.lon[::-1], self.lat[::-1]) expected = self.distance * self.distance[::-1] * np.sin(sep) - assert_quantity_allclose(r_cross_r_rev.norm(), expected, - atol=1.*u.mpc**2) + assert_quantity_allclose(r_cross_r_rev.norm(), expected, atol=1.0 * u.mpc**2) unit_vectors = CartesianRepresentation( - [1., 0., 0.]*u.one, - [0., 1., 0.]*u.one, - [0., 0., 1.]*u.one)[:, np.newaxis] + [1.0, 0.0, 0.0] * u.one, [0.0, 1.0, 0.0] * u.one, [0.0, 0.0, 1.0] * u.one + )[:, np.newaxis] r_cross_uv = in_rep.cross(unit_vectors) assert r_cross_uv.shape == (3, 7) - assert_quantity_allclose(r_cross_uv.dot(unit_vectors), 0.*u.kpc, - atol=1.*u.upc) - assert_quantity_allclose(r_cross_uv.dot(in_rep), 0.*u.kpc**2, - atol=1.*u.mpc**2) + assert_quantity_allclose( + r_cross_uv.dot(unit_vectors), 0.0 * u.kpc, atol=1.0 * u.upc + ) + assert_quantity_allclose( + r_cross_uv.dot(in_rep), 0.0 * u.kpc**2, atol=1.0 * u.mpc**2 + ) zeros = np.zeros(len(in_rep)) * u.kpc expected = CartesianRepresentation( u.Quantity((zeros, -self.cartesian.z, self.cartesian.y)), u.Quantity((self.cartesian.z, zeros, -self.cartesian.x)), - u.Quantity((-self.cartesian.y, self.cartesian.x, zeros))) + u.Quantity((-self.cartesian.y, self.cartesian.x, zeros)), + ) # Comparison with spherical is hard since some distances are zero, # implying the angles are undefined. r_cross_uv_cartesian = r_cross_uv.to_cartesian() - assert_representation_allclose(r_cross_uv_cartesian, - expected, atol=1.*u.upc) + assert_representation_allclose(r_cross_uv_cartesian, expected, atol=1.0 * u.upc) # A final check, with the side benefit of ensuring __truediv__ and norm # work on multi-D representations. r_cross_uv_by_distance = r_cross_uv / self.distance uv_sph = unit_vectors.represent_as(UnitSphericalRepresentation) sep = angular_separation(self.lon, self.lat, uv_sph.lon, uv_sph.lat) - assert_quantity_allclose(r_cross_uv_by_distance.norm(), np.sin(sep), - atol=1e-9) + assert_quantity_allclose(r_cross_uv_by_distance.norm(), np.sin(sep), atol=1e-9) with pytest.raises(TypeError): in_rep.cross(self.cartesian.xyz) @@ -389,23 +439,20 @@ def test_cross(self, representation): def test_cross_unit_spherical(self): u_cross_u = self.unit_spherical.cross(self.unit_spherical) assert isinstance(u_cross_u, SphericalRepresentation) - assert_quantity_allclose(u_cross_u.norm(), 0.*u.one, atol=1.e-10*u.one) + assert_quantity_allclose(u_cross_u.norm(), 0.0 * u.one, atol=1.0e-10 * u.one) u_cross_u_rev = self.unit_spherical.cross(self.unit_spherical[::-1]) assert isinstance(u_cross_u_rev, SphericalRepresentation) - sep = angular_separation(self.lon, self.lat, - self.lon[::-1], self.lat[::-1]) + sep = angular_separation(self.lon, self.lat, self.lon[::-1], self.lat[::-1]) expected = np.sin(sep) - assert_quantity_allclose(u_cross_u_rev.norm(), expected, - atol=1.e-10*u.one) + assert_quantity_allclose(u_cross_u_rev.norm(), expected, atol=1.0e-10 * u.one) -class TestUnitVectorsAndScales(): - +class TestUnitVectorsAndScales: @staticmethod def check_unit_vectors(e): for v in e.values(): assert type(v) is CartesianRepresentation - assert_quantity_allclose(v.norm(), 1. * u.one) + assert_quantity_allclose(v.norm(), 1.0 * u.one) return e @staticmethod @@ -416,153 +463,152 @@ def check_scale_factors(sf, rep): assert (f.unit * getattr(rep, c).unit).is_equivalent(unit) def test_spherical(self): - s = SphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg, - distance=[1, 2, 3] * u.kpc) + s = SphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, + lat=[0.0, -30.0, 85.0] * u.deg, + distance=[1, 2, 3] * u.kpc, + ) e = s.unit_vectors() self.check_unit_vectors(e) sf = s.scale_factors() self.check_scale_factors(sf, s) - s_lon = s + s.distance * 1e-5 * np.cos(s.lat) * e['lon'] - assert_quantity_allclose(s_lon.lon, s.lon + 1e-5*u.rad, - atol=1e-10*u.rad) - assert_quantity_allclose(s_lon.lat, s.lat, atol=1e-10*u.rad) + s_lon = s + s.distance * 1e-5 * np.cos(s.lat) * e["lon"] + assert_quantity_allclose(s_lon.lon, s.lon + 1e-5 * u.rad, atol=1e-10 * u.rad) + assert_quantity_allclose(s_lon.lat, s.lat, atol=1e-10 * u.rad) assert_quantity_allclose(s_lon.distance, s.distance) - s_lon2 = s + 1e-5 * u.radian * sf['lon'] * e['lon'] + s_lon2 = s + 1e-5 * u.radian * sf["lon"] * e["lon"] assert_representation_allclose(s_lon2, s_lon) - s_lat = s + s.distance * 1e-5 * e['lat'] + s_lat = s + s.distance * 1e-5 * e["lat"] assert_quantity_allclose(s_lat.lon, s.lon) - assert_quantity_allclose(s_lat.lat, s.lat + 1e-5*u.rad, - atol=1e-10*u.rad) + assert_quantity_allclose(s_lat.lat, s.lat + 1e-5 * u.rad, atol=1e-10 * u.rad) assert_quantity_allclose(s_lon.distance, s.distance) - s_lat2 = s + 1.e-5 * u.radian * sf['lat'] * e['lat'] + s_lat2 = s + 1.0e-5 * u.radian * sf["lat"] * e["lat"] assert_representation_allclose(s_lat2, s_lat) - s_distance = s + 1. * u.pc * e['distance'] - assert_quantity_allclose(s_distance.lon, s.lon, atol=1e-10*u.rad) - assert_quantity_allclose(s_distance.lat, s.lat, atol=1e-10*u.rad) - assert_quantity_allclose(s_distance.distance, s.distance + 1.*u.pc) - s_distance2 = s + 1. * u.pc * sf['distance'] * e['distance'] + s_distance = s + 1.0 * u.pc * e["distance"] + assert_quantity_allclose(s_distance.lon, s.lon, atol=1e-10 * u.rad) + assert_quantity_allclose(s_distance.lat, s.lat, atol=1e-10 * u.rad) + assert_quantity_allclose(s_distance.distance, s.distance + 1.0 * u.pc) + s_distance2 = s + 1.0 * u.pc * sf["distance"] * e["distance"] assert_representation_allclose(s_distance2, s_distance) def test_unit_spherical(self): - s = UnitSphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg) + s = UnitSphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, lat=[0.0, -30.0, 85.0] * u.deg + ) e = s.unit_vectors() self.check_unit_vectors(e) sf = s.scale_factors() self.check_scale_factors(sf, s) - s_lon = s + 1e-5 * np.cos(s.lat) * e['lon'] - assert_quantity_allclose(s_lon.lon, s.lon + 1e-5*u.rad, - atol=1e-10*u.rad) - assert_quantity_allclose(s_lon.lat, s.lat, atol=1e-10*u.rad) - s_lon2 = s + 1e-5 * u.radian * sf['lon'] * e['lon'] + s_lon = s + 1e-5 * np.cos(s.lat) * e["lon"] + assert_quantity_allclose(s_lon.lon, s.lon + 1e-5 * u.rad, atol=1e-10 * u.rad) + assert_quantity_allclose(s_lon.lat, s.lat, atol=1e-10 * u.rad) + s_lon2 = s + 1e-5 * u.radian * sf["lon"] * e["lon"] assert_representation_allclose(s_lon2, s_lon) - s_lat = s + 1e-5 * e['lat'] + s_lat = s + 1e-5 * e["lat"] assert_quantity_allclose(s_lat.lon, s.lon) - assert_quantity_allclose(s_lat.lat, s.lat + 1e-5*u.rad, - atol=1e-10*u.rad) - s_lat2 = s + 1.e-5 * u.radian * sf['lat'] * e['lat'] + assert_quantity_allclose(s_lat.lat, s.lat + 1e-5 * u.rad, atol=1e-10 * u.rad) + s_lat2 = s + 1.0e-5 * u.radian * sf["lat"] * e["lat"] assert_representation_allclose(s_lat2, s_lat) def test_radial(self): - r = RadialRepresentation(10.*u.kpc) + r = RadialRepresentation(10.0 * u.kpc) with pytest.raises(NotImplementedError): r.unit_vectors() sf = r.scale_factors() - assert np.all(sf['distance'] == 1.*u.one) + assert np.all(sf["distance"] == 1.0 * u.one) assert np.all(r.norm() == r.distance) with pytest.raises(TypeError): r + r def test_physical_spherical(self): - - s = PhysicsSphericalRepresentation(phi=[0., 6., 21.] * u.hourangle, - theta=[90., 120., 5.] * u.deg, - r=[1, 2, 3] * u.kpc) + s = PhysicsSphericalRepresentation( + phi=[0.0, 6.0, 21.0] * u.hourangle, + theta=[90.0, 120.0, 5.0] * u.deg, + r=[1, 2, 3] * u.kpc, + ) e = s.unit_vectors() self.check_unit_vectors(e) sf = s.scale_factors() self.check_scale_factors(sf, s) - s_phi = s + s.r * 1e-5 * np.sin(s.theta) * e['phi'] - assert_quantity_allclose(s_phi.phi, s.phi + 1e-5*u.rad, - atol=1e-10*u.rad) - assert_quantity_allclose(s_phi.theta, s.theta, atol=1e-10*u.rad) + s_phi = s + s.r * 1e-5 * np.sin(s.theta) * e["phi"] + assert_quantity_allclose(s_phi.phi, s.phi + 1e-5 * u.rad, atol=1e-10 * u.rad) + assert_quantity_allclose(s_phi.theta, s.theta, atol=1e-10 * u.rad) assert_quantity_allclose(s_phi.r, s.r) - s_phi2 = s + 1e-5 * u.radian * sf['phi'] * e['phi'] + s_phi2 = s + 1e-5 * u.radian * sf["phi"] * e["phi"] assert_representation_allclose(s_phi2, s_phi) - s_theta = s + s.r * 1e-5 * e['theta'] + s_theta = s + s.r * 1e-5 * e["theta"] assert_quantity_allclose(s_theta.phi, s.phi) - assert_quantity_allclose(s_theta.theta, s.theta + 1e-5*u.rad, - atol=1e-10*u.rad) + assert_quantity_allclose( + s_theta.theta, s.theta + 1e-5 * u.rad, atol=1e-10 * u.rad + ) assert_quantity_allclose(s_theta.r, s.r) - s_theta2 = s + 1.e-5 * u.radian * sf['theta'] * e['theta'] + s_theta2 = s + 1.0e-5 * u.radian * sf["theta"] * e["theta"] assert_representation_allclose(s_theta2, s_theta) - s_r = s + 1. * u.pc * e['r'] - assert_quantity_allclose(s_r.phi, s.phi, atol=1e-10*u.rad) - assert_quantity_allclose(s_r.theta, s.theta, atol=1e-10*u.rad) - assert_quantity_allclose(s_r.r, s.r + 1.*u.pc) - s_r2 = s + 1. * u.pc * sf['r'] * e['r'] + s_r = s + 1.0 * u.pc * e["r"] + assert_quantity_allclose(s_r.phi, s.phi, atol=1e-10 * u.rad) + assert_quantity_allclose(s_r.theta, s.theta, atol=1e-10 * u.rad) + assert_quantity_allclose(s_r.r, s.r + 1.0 * u.pc) + s_r2 = s + 1.0 * u.pc * sf["r"] * e["r"] assert_representation_allclose(s_r2, s_r) def test_cartesian(self): - - s = CartesianRepresentation(x=[1, 2, 3] * u.pc, - y=[2, 3, 4] * u.Mpc, - z=[3, 4, 5] * u.kpc) + s = CartesianRepresentation( + x=[1, 2, 3] * u.pc, y=[2, 3, 4] * u.Mpc, z=[3, 4, 5] * u.kpc + ) e = s.unit_vectors() sf = s.scale_factors() - for v, expected in zip(e.values(), ([1., 0., 0.] * u.one, - [0., 1., 0.] * u.one, - [0., 0., 1.] * u.one)): + for v, expected in zip( + e.values(), + ([1.0, 0.0, 0.0] * u.one, [0.0, 1.0, 0.0] * u.one, [0.0, 0.0, 1.0] * u.one), + ): assert np.all(v.get_xyz(xyz_axis=-1) == expected) for f in sf.values(): - assert np.all(f == 1.*u.one) + assert np.all(f == 1.0 * u.one) def test_cylindrical(self): - - s = CylindricalRepresentation(rho=[1, 2, 3] * u.pc, - phi=[0., 90., -45.] * u.deg, - z=[3, 4, 5] * u.kpc) + s = CylindricalRepresentation( + rho=[1, 2, 3] * u.pc, phi=[0.0, 90.0, -45.0] * u.deg, z=[3, 4, 5] * u.kpc + ) e = s.unit_vectors() self.check_unit_vectors(e) sf = s.scale_factors() self.check_scale_factors(sf, s) - s_rho = s + 1. * u.pc * e['rho'] - assert_quantity_allclose(s_rho.rho, s.rho + 1.*u.pc) + s_rho = s + 1.0 * u.pc * e["rho"] + assert_quantity_allclose(s_rho.rho, s.rho + 1.0 * u.pc) assert_quantity_allclose(s_rho.phi, s.phi) assert_quantity_allclose(s_rho.z, s.z) - s_rho2 = s + 1. * u.pc * sf['rho'] * e['rho'] + s_rho2 = s + 1.0 * u.pc * sf["rho"] * e["rho"] assert_representation_allclose(s_rho2, s_rho) - s_phi = s + s.rho * 1e-5 * e['phi'] + s_phi = s + s.rho * 1e-5 * e["phi"] assert_quantity_allclose(s_phi.rho, s.rho) - assert_quantity_allclose(s_phi.phi, s.phi + 1e-5*u.rad) + assert_quantity_allclose(s_phi.phi, s.phi + 1e-5 * u.rad) assert_quantity_allclose(s_phi.z, s.z) - s_phi2 = s + 1e-5 * u.radian * sf['phi'] * e['phi'] + s_phi2 = s + 1e-5 * u.radian * sf["phi"] * e["phi"] assert_representation_allclose(s_phi2, s_phi) - s_z = s + 1. * u.pc * e['z'] + s_z = s + 1.0 * u.pc * e["z"] assert_quantity_allclose(s_z.rho, s.rho) - assert_quantity_allclose(s_z.phi, s.phi, atol=1e-10*u.rad) - assert_quantity_allclose(s_z.z, s.z + 1.*u.pc) - s_z2 = s + 1. * u.pc * sf['z'] * e['z'] + assert_quantity_allclose(s_z.phi, s.phi, atol=1e-10 * u.rad) + assert_quantity_allclose(s_z.z, s.z + 1.0 * u.pc) + s_z2 = s + 1.0 * u.pc * sf["z"] * e["z"] assert_representation_allclose(s_z2, s_z) -@pytest.mark.parametrize('omit_coslat', [False, True], scope='class') -class TestSphericalDifferential(): +@pytest.mark.parametrize("omit_coslat", [False, True], scope="class") +class TestSphericalDifferential: # these test cases are subclassed for SphericalCosLatDifferential, # hence some tests depend on omit_coslat. @@ -572,9 +618,11 @@ def _setup(self, omit_coslat): else: self.SD_cls = SphericalDifferential - s = SphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg, - distance=[1, 2, 3] * u.kpc) + s = SphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, + lat=[0.0, -30.0, 85.0] * u.deg, + distance=[1, 2, 3] * u.kpc, + ) self.s = s self.e = s.unit_vectors() self.sf = s.scale_factors(omit_coslat=omit_coslat) @@ -583,47 +631,48 @@ def test_name_coslat(self, omit_coslat): self._setup(omit_coslat) if omit_coslat: assert self.SD_cls is SphericalCosLatDifferential - assert self.SD_cls.get_name() == 'sphericalcoslat' + assert self.SD_cls.get_name() == "sphericalcoslat" else: assert self.SD_cls is SphericalDifferential - assert self.SD_cls.get_name() == 'spherical' + assert self.SD_cls.get_name() == "spherical" assert self.SD_cls.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self, omit_coslat): self._setup(omit_coslat) s, e, sf = self.s, self.e, self.sf - o_lon = self.SD_cls(1.*u.arcsec, 0.*u.arcsec, 0.*u.kpc) + o_lon = self.SD_cls(1.0 * u.arcsec, 0.0 * u.arcsec, 0.0 * u.kpc) o_lonc = o_lon.to_cartesian(base=s) o_lon2 = self.SD_cls.from_cartesian(o_lonc, base=s) assert_differential_allclose(o_lon, o_lon2) # simple check by hand for first element. # lat[0] is 0, so cos(lat) term doesn't matter. - assert_quantity_allclose(o_lonc[0].xyz, - [0., np.pi/180./3600., 0.]*u.kpc) + assert_quantity_allclose( + o_lonc[0].xyz, [0.0, np.pi / 180.0 / 3600.0, 0.0] * u.kpc + ) # check all using unit vectors and scale factors. - s_lon = s + 1.*u.arcsec * sf['lon'] * e['lon'] - assert_representation_allclose(o_lonc, s_lon - s, atol=1*u.npc) + s_lon = s + 1.0 * u.arcsec * sf["lon"] * e["lon"] + assert_representation_allclose(o_lonc, s_lon - s, atol=1 * u.npc) s_lon2 = s + o_lon - assert_representation_allclose(s_lon2, s_lon, atol=1*u.npc) + assert_representation_allclose(s_lon2, s_lon, atol=1 * u.npc) - o_lat = self.SD_cls(0.*u.arcsec, 1.*u.arcsec, 0.*u.kpc) + o_lat = self.SD_cls(0.0 * u.arcsec, 1.0 * u.arcsec, 0.0 * u.kpc) o_latc = o_lat.to_cartesian(base=s) - assert_quantity_allclose(o_latc[0].xyz, - [0., 0., np.pi/180./3600.]*u.kpc, - atol=1.*u.npc) - s_lat = s + 1.*u.arcsec * sf['lat'] * e['lat'] - assert_representation_allclose(o_latc, s_lat - s, atol=1*u.npc) + assert_quantity_allclose( + o_latc[0].xyz, [0.0, 0.0, np.pi / 180.0 / 3600.0] * u.kpc, atol=1.0 * u.npc + ) + s_lat = s + 1.0 * u.arcsec * sf["lat"] * e["lat"] + assert_representation_allclose(o_latc, s_lat - s, atol=1 * u.npc) s_lat2 = s + o_lat - assert_representation_allclose(s_lat2, s_lat, atol=1*u.npc) + assert_representation_allclose(s_lat2, s_lat, atol=1 * u.npc) - o_distance = self.SD_cls(0.*u.arcsec, 0.*u.arcsec, 1.*u.mpc) + o_distance = self.SD_cls(0.0 * u.arcsec, 0.0 * u.arcsec, 1.0 * u.mpc) o_distancec = o_distance.to_cartesian(base=s) - assert_quantity_allclose(o_distancec[0].xyz, - [1e-6, 0., 0.]*u.kpc, atol=1.*u.npc) - s_distance = s + 1.*u.mpc * sf['distance'] * e['distance'] - assert_representation_allclose(o_distancec, s_distance - s, - atol=1*u.npc) + assert_quantity_allclose( + o_distancec[0].xyz, [1e-6, 0.0, 0.0] * u.kpc, atol=1.0 * u.npc + ) + s_distance = s + 1.0 * u.mpc * sf["distance"] * e["distance"] + assert_representation_allclose(o_distancec, s_distance - s, atol=1 * u.npc) s_distance2 = s + o_distance assert_representation_allclose(s_distance2, s_distance) @@ -631,45 +680,57 @@ def test_differential_arithmetic(self, omit_coslat): self._setup(omit_coslat) s = self.s - o_lon = self.SD_cls(1.*u.arcsec, 0.*u.arcsec, 0.*u.kpc) - o_lon_by_2 = o_lon / 2. - assert_representation_allclose(o_lon_by_2.to_cartesian(s) * 2., - o_lon.to_cartesian(s), atol=1e-10*u.kpc) - assert_representation_allclose(s + o_lon, s + 2 * o_lon_by_2, - atol=1e-10*u.kpc) + o_lon = self.SD_cls(1.0 * u.arcsec, 0.0 * u.arcsec, 0.0 * u.kpc) + o_lon_by_2 = o_lon / 2.0 + assert_representation_allclose( + o_lon_by_2.to_cartesian(s) * 2.0, o_lon.to_cartesian(s), atol=1e-10 * u.kpc + ) + assert_representation_allclose( + s + o_lon, s + 2 * o_lon_by_2, atol=1e-10 * u.kpc + ) o_lon_rec = o_lon_by_2 + o_lon_by_2 - assert_representation_allclose(s + o_lon, s + o_lon_rec, - atol=1e-10*u.kpc) + assert_representation_allclose(s + o_lon, s + o_lon_rec, atol=1e-10 * u.kpc) o_lon_0 = o_lon - o_lon for c in o_lon_0.components: - assert np.all(getattr(o_lon_0, c) == 0.) - o_lon2 = self.SD_cls(1*u.mas/u.yr, 0*u.mas/u.yr, 0*u.km/u.s) - assert_quantity_allclose(o_lon2.norm(s)[0], 4.74*u.km/u.s, - atol=0.01*u.km/u.s) - assert_representation_allclose(o_lon2.to_cartesian(s) * 1000.*u.yr, - o_lon.to_cartesian(s), atol=1e-10*u.kpc) + assert np.all(getattr(o_lon_0, c) == 0.0) + o_lon2 = self.SD_cls(1 * u.mas / u.yr, 0 * u.mas / u.yr, 0 * u.km / u.s) + assert_quantity_allclose( + o_lon2.norm(s)[0], 4.74 * u.km / u.s, atol=0.01 * u.km / u.s + ) + assert_representation_allclose( + o_lon2.to_cartesian(s) * 1000.0 * u.yr, + o_lon.to_cartesian(s), + atol=1e-10 * u.kpc, + ) s_off = s + o_lon - s_off2 = s + o_lon2 * 1000.*u.yr - assert_representation_allclose(s_off, s_off2, atol=1e-10*u.kpc) + s_off2 = s + o_lon2 * 1000.0 * u.yr + assert_representation_allclose(s_off, s_off2, atol=1e-10 * u.kpc) - factor = 1e5 * u.radian/u.arcsec + factor = 1e5 * u.radian / u.arcsec if not omit_coslat: factor = factor / np.cos(s.lat) s_off_big = s + o_lon * factor assert_representation_allclose( - s_off_big, SphericalRepresentation(s.lon + 90.*u.deg, 0.*u.deg, - 1e5*s.distance), - atol=5.*u.kpc) - - o_lon3c = CartesianRepresentation(0., 4.74047, 0., unit=u.km/u.s) + s_off_big, + SphericalRepresentation( + s.lon + 90.0 * u.deg, 0.0 * u.deg, 1e5 * s.distance + ), + atol=5.0 * u.kpc, + ) + + o_lon3c = CartesianRepresentation(0.0, 4.74047, 0.0, unit=u.km / u.s) o_lon3 = self.SD_cls.from_cartesian(o_lon3c, base=s) - expected0 = self.SD_cls(1.*u.mas/u.yr, 0.*u.mas/u.yr, 0.*u.km/u.s) + expected0 = self.SD_cls( + 1.0 * u.mas / u.yr, 0.0 * u.mas / u.yr, 0.0 * u.km / u.s + ) assert_differential_allclose(o_lon3[0], expected0) - s_off_big2 = s + o_lon3 * 1e5 * u.yr * u.radian/u.mas + s_off_big2 = s + o_lon3 * 1e5 * u.yr * u.radian / u.mas assert_representation_allclose( - s_off_big2, SphericalRepresentation(90.*u.deg, 0.*u.deg, - 1e5*u.kpc), atol=5.*u.kpc) + s_off_big2, + SphericalRepresentation(90.0 * u.deg, 0.0 * u.deg, 1e5 * u.kpc), + atol=5.0 * u.kpc, + ) with pytest.raises(TypeError): o_lon - s @@ -680,35 +741,41 @@ def test_differential_init_errors(self, omit_coslat): self._setup(omit_coslat) s = self.s with pytest.raises(u.UnitsError): - self.SD_cls(1.*u.arcsec, 0., 0.) + self.SD_cls(1.0 * u.arcsec, 0.0, 0.0) with pytest.raises(TypeError): - self.SD_cls(1.*u.arcsec, 0.*u.arcsec, 0.*u.kpc, - False, False) + self.SD_cls(1.0 * u.arcsec, 0.0 * u.arcsec, 0.0 * u.kpc, False, False) with pytest.raises(TypeError): - self.SD_cls(1.*u.arcsec, 0.*u.arcsec, 0.*u.kpc, - copy=False, d_lat=0.*u.arcsec) + self.SD_cls( + 1.0 * u.arcsec, + 0.0 * u.arcsec, + 0.0 * u.kpc, + copy=False, + d_lat=0.0 * u.arcsec, + ) with pytest.raises(TypeError): - self.SD_cls(1.*u.arcsec, 0.*u.arcsec, 0.*u.kpc, - copy=False, flying='circus') + self.SD_cls( + 1.0 * u.arcsec, 0.0 * u.arcsec, 0.0 * u.kpc, copy=False, flying="circus" + ) with pytest.raises(ValueError): - self.SD_cls(np.ones(2)*u.arcsec, - np.zeros(3)*u.arcsec, np.zeros(2)*u.kpc) + self.SD_cls( + np.ones(2) * u.arcsec, np.zeros(3) * u.arcsec, np.zeros(2) * u.kpc + ) with pytest.raises(u.UnitsError): - self.SD_cls(1.*u.arcsec, 1.*u.s, 0.*u.kpc) + self.SD_cls(1.0 * u.arcsec, 1.0 * u.s, 0.0 * u.kpc) with pytest.raises(u.UnitsError): - self.SD_cls(1.*u.kpc, 1.*u.arcsec, 0.*u.kpc) - o = self.SD_cls(1.*u.arcsec, 1.*u.arcsec, 0.*u.km/u.s) + self.SD_cls(1.0 * u.kpc, 1.0 * u.arcsec, 0.0 * u.kpc) + o = self.SD_cls(1.0 * u.arcsec, 1.0 * u.arcsec, 0.0 * u.km / u.s) with pytest.raises(u.UnitsError): o.to_cartesian(s) with pytest.raises(AttributeError): - o.d_lat = 0.*u.arcsec + o.d_lat = 0.0 * u.arcsec with pytest.raises(AttributeError): del o.d_lat - o = self.SD_cls(1.*u.arcsec, 1.*u.arcsec, 0.*u.km) + o = self.SD_cls(1.0 * u.arcsec, 1.0 * u.arcsec, 0.0 * u.km) with pytest.raises(TypeError): o.to_cartesian() - c = CartesianRepresentation(10., 0., 0., unit=u.km) + c = CartesianRepresentation(10.0, 0.0, 0.0, unit=u.km) with pytest.raises(TypeError): self.SD_cls.to_cartesian(c) with pytest.raises(TypeError): @@ -717,16 +784,17 @@ def test_differential_init_errors(self, omit_coslat): self.SD_cls.from_cartesian(c, SphericalRepresentation) -@pytest.mark.parametrize('omit_coslat', [False, True], scope='class') -class TestUnitSphericalDifferential(): +@pytest.mark.parametrize("omit_coslat", [False, True], scope="class") +class TestUnitSphericalDifferential: def _setup(self, omit_coslat): if omit_coslat: self.USD_cls = UnitSphericalCosLatDifferential else: self.USD_cls = UnitSphericalDifferential - s = UnitSphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg) + s = UnitSphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, lat=[0.0, -30.0, 85.0] * u.deg + ) self.s = s self.e = s.unit_vectors() self.sf = s.scale_factors(omit_coslat=omit_coslat) @@ -735,320 +803,343 @@ def test_name_coslat(self, omit_coslat): self._setup(omit_coslat) if omit_coslat: assert self.USD_cls is UnitSphericalCosLatDifferential - assert self.USD_cls.get_name() == 'unitsphericalcoslat' + assert self.USD_cls.get_name() == "unitsphericalcoslat" else: assert self.USD_cls is UnitSphericalDifferential - assert self.USD_cls.get_name() == 'unitspherical' + assert self.USD_cls.get_name() == "unitspherical" assert self.USD_cls.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self, omit_coslat): self._setup(omit_coslat) s, e, sf = self.s, self.e, self.sf - o_lon = self.USD_cls(1.*u.arcsec, 0.*u.arcsec) + o_lon = self.USD_cls(1.0 * u.arcsec, 0.0 * u.arcsec) o_lonc = o_lon.to_cartesian(base=s) o_lon2 = self.USD_cls.from_cartesian(o_lonc, base=s) assert_differential_allclose(o_lon, o_lon2) # simple check by hand for first element # (lat[0]=0, so works for both normal and CosLat differential) - assert_quantity_allclose(o_lonc[0].xyz, - [0., np.pi/180./3600., 0.]*u.one) + assert_quantity_allclose( + o_lonc[0].xyz, [0.0, np.pi / 180.0 / 3600.0, 0.0] * u.one + ) # check all using unit vectors and scale factors. - s_lon = s + 1.*u.arcsec * sf['lon'] * e['lon'] + s_lon = s + 1.0 * u.arcsec * sf["lon"] * e["lon"] assert type(s_lon) is SphericalRepresentation - assert_representation_allclose(o_lonc, s_lon - s, atol=1e-10*u.one) + assert_representation_allclose(o_lonc, s_lon - s, atol=1e-10 * u.one) s_lon2 = s + o_lon - assert_representation_allclose(s_lon2, s_lon, atol=1e-10*u.one) + assert_representation_allclose(s_lon2, s_lon, atol=1e-10 * u.one) - o_lat = self.USD_cls(0.*u.arcsec, 1.*u.arcsec) + o_lat = self.USD_cls(0.0 * u.arcsec, 1.0 * u.arcsec) o_latc = o_lat.to_cartesian(base=s) - assert_quantity_allclose(o_latc[0].xyz, - [0., 0., np.pi/180./3600.]*u.one, - atol=1e-10*u.one) - s_lat = s + 1.*u.arcsec * sf['lat'] * e['lat'] + assert_quantity_allclose( + o_latc[0].xyz, + [0.0, 0.0, np.pi / 180.0 / 3600.0] * u.one, + atol=1e-10 * u.one, + ) + s_lat = s + 1.0 * u.arcsec * sf["lat"] * e["lat"] assert type(s_lat) is SphericalRepresentation - assert_representation_allclose(o_latc, s_lat - s, atol=1e-10*u.one) + assert_representation_allclose(o_latc, s_lat - s, atol=1e-10 * u.one) s_lat2 = s + o_lat - assert_representation_allclose(s_lat2, s_lat, atol=1e-10*u.one) + assert_representation_allclose(s_lat2, s_lat, atol=1e-10 * u.one) def test_differential_arithmetic(self, omit_coslat): self._setup(omit_coslat) s = self.s - o_lon = self.USD_cls(1.*u.arcsec, 0.*u.arcsec) - o_lon_by_2 = o_lon / 2. + o_lon = self.USD_cls(1.0 * u.arcsec, 0.0 * u.arcsec) + o_lon_by_2 = o_lon / 2.0 assert type(o_lon_by_2) is self.USD_cls - assert_representation_allclose(o_lon_by_2.to_cartesian(s) * 2., - o_lon.to_cartesian(s), atol=1e-10*u.one) + assert_representation_allclose( + o_lon_by_2.to_cartesian(s) * 2.0, o_lon.to_cartesian(s), atol=1e-10 * u.one + ) s_lon = s + o_lon s_lon2 = s + 2 * o_lon_by_2 assert type(s_lon) is SphericalRepresentation - assert_representation_allclose(s_lon, s_lon2, atol=1e-10*u.one) + assert_representation_allclose(s_lon, s_lon2, atol=1e-10 * u.one) o_lon_rec = o_lon_by_2 + o_lon_by_2 assert type(o_lon_rec) is self.USD_cls assert representation_equal(o_lon, o_lon_rec) - assert_representation_allclose(s + o_lon, s + o_lon_rec, - atol=1e-10*u.one) + assert_representation_allclose(s + o_lon, s + o_lon_rec, atol=1e-10 * u.one) o_lon_0 = o_lon - o_lon assert type(o_lon_0) is self.USD_cls for c in o_lon_0.components: - assert np.all(getattr(o_lon_0, c) == 0.) + assert np.all(getattr(o_lon_0, c) == 0.0) - o_lon2 = self.USD_cls(1.*u.mas/u.yr, 0.*u.mas/u.yr) - kks = u.km/u.kpc/u.s - assert_quantity_allclose(o_lon2.norm(s)[0], 4.74047*kks, atol=1e-4*kks) - assert_representation_allclose(o_lon2.to_cartesian(s) * 1000.*u.yr, - o_lon.to_cartesian(s), atol=1e-10*u.one) + o_lon2 = self.USD_cls(1.0 * u.mas / u.yr, 0.0 * u.mas / u.yr) + kks = u.km / u.kpc / u.s + assert_quantity_allclose(o_lon2.norm(s)[0], 4.74047 * kks, atol=1e-4 * kks) + assert_representation_allclose( + o_lon2.to_cartesian(s) * 1000.0 * u.yr, + o_lon.to_cartesian(s), + atol=1e-10 * u.one, + ) s_off = s + o_lon - s_off2 = s + o_lon2 * 1000.*u.yr - assert_representation_allclose(s_off, s_off2, atol=1e-10*u.one) + s_off2 = s + o_lon2 * 1000.0 * u.yr + assert_representation_allclose(s_off, s_off2, atol=1e-10 * u.one) - factor = 1e5 * u.radian/u.arcsec + factor = 1e5 * u.radian / u.arcsec if not omit_coslat: factor = factor / np.cos(s.lat) s_off_big = s + o_lon * factor assert_representation_allclose( - s_off_big, SphericalRepresentation(s.lon + 90.*u.deg, - 0.*u.deg, 1e5), - atol=5.*u.one) + s_off_big, + SphericalRepresentation(s.lon + 90.0 * u.deg, 0.0 * u.deg, 1e5), + atol=5.0 * u.one, + ) - o_lon3c = CartesianRepresentation(0., 4.74047, 0., unit=kks) + o_lon3c = CartesianRepresentation(0.0, 4.74047, 0.0, unit=kks) # This looses information!! o_lon3 = self.USD_cls.from_cartesian(o_lon3c, base=s) - expected0 = self.USD_cls(1.*u.mas/u.yr, 0.*u.mas/u.yr) + expected0 = self.USD_cls(1.0 * u.mas / u.yr, 0.0 * u.mas / u.yr) assert_differential_allclose(o_lon3[0], expected0) # Part of motion kept. part_kept = s.cross(CartesianRepresentation(0, 1, 0, unit=u.one)).norm() - assert_quantity_allclose(o_lon3.norm(s), 4.74047*part_kept*kks, - atol=1e-10*kks) + assert_quantity_allclose( + o_lon3.norm(s), 4.74047 * part_kept * kks, atol=1e-10 * kks + ) # (lat[0]=0, so works for both normal and CosLat differential) - s_off_big2 = s + o_lon3 * 1e5 * u.yr * u.radian/u.mas - expected0 = SphericalRepresentation(90.*u.deg, 0.*u.deg, - 1e5*u.one) - assert_representation_allclose(s_off_big2[0], expected0, atol=5.*u.one) + s_off_big2 = s + o_lon3 * 1e5 * u.yr * u.radian / u.mas + expected0 = SphericalRepresentation(90.0 * u.deg, 0.0 * u.deg, 1e5 * u.one) + assert_representation_allclose(s_off_big2[0], expected0, atol=5.0 * u.one) def test_differential_init_errors(self, omit_coslat): self._setup(omit_coslat) with pytest.raises(u.UnitsError): - self.USD_cls(0.*u.deg, 10.*u.deg/u.yr) + self.USD_cls(0.0 * u.deg, 10.0 * u.deg / u.yr) -class TestRadialDifferential(): +class TestRadialDifferential: def setup_method(self): - s = SphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg, - distance=[1, 2, 3] * u.kpc) + s = SphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, + lat=[0.0, -30.0, 85.0] * u.deg, + distance=[1, 2, 3] * u.kpc, + ) self.s = s self.r = s.represent_as(RadialRepresentation) self.e = s.unit_vectors() self.sf = s.scale_factors() def test_name(self): - assert RadialDifferential.get_name() == 'radial' + assert RadialDifferential.get_name() == "radial" assert RadialDifferential.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self): r, s, e, sf = self.r, self.s, self.e, self.sf - o_distance = RadialDifferential(1.*u.mpc) + o_distance = RadialDifferential(1.0 * u.mpc) # Can be applied to RadialRepresentation, though not most useful. r_distance = r + o_distance - assert_quantity_allclose(r_distance.distance, - r.distance + o_distance.d_distance) + assert_quantity_allclose( + r_distance.distance, r.distance + o_distance.d_distance + ) r_distance2 = o_distance + r - assert_quantity_allclose(r_distance2.distance, - r.distance + o_distance.d_distance) + assert_quantity_allclose( + r_distance2.distance, r.distance + o_distance.d_distance + ) # More sense to apply it relative to spherical representation. o_distancec = o_distance.to_cartesian(base=s) - assert_quantity_allclose(o_distancec[0].xyz, - [1e-6, 0., 0.]*u.kpc, atol=1.*u.npc) + assert_quantity_allclose( + o_distancec[0].xyz, [1e-6, 0.0, 0.0] * u.kpc, atol=1.0 * u.npc + ) o_recover = RadialDifferential.from_cartesian(o_distancec, base=s) assert_quantity_allclose(o_recover.d_distance, o_distance.d_distance) - s_distance = s + 1.*u.mpc * sf['distance'] * e['distance'] - assert_representation_allclose(o_distancec, s_distance - s, - atol=1*u.npc) + s_distance = s + 1.0 * u.mpc * sf["distance"] * e["distance"] + assert_representation_allclose(o_distancec, s_distance - s, atol=1 * u.npc) s_distance2 = s + o_distance assert_representation_allclose(s_distance2, s_distance) -class TestPhysicsSphericalDifferential(): +class TestPhysicsSphericalDifferential: """Test copied from SphericalDifferential, so less extensive.""" def setup_method(self): - s = PhysicsSphericalRepresentation(phi=[0., 90., 315.] * u.deg, - theta=[90., 120., 5.] * u.deg, - r=[1, 2, 3] * u.kpc) + s = PhysicsSphericalRepresentation( + phi=[0.0, 90.0, 315.0] * u.deg, + theta=[90.0, 120.0, 5.0] * u.deg, + r=[1, 2, 3] * u.kpc, + ) self.s = s self.e = s.unit_vectors() self.sf = s.scale_factors() def test_name(self): - assert PhysicsSphericalDifferential.get_name() == 'physicsspherical' + assert PhysicsSphericalDifferential.get_name() == "physicsspherical" assert PhysicsSphericalDifferential.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self): s, e, sf = self.s, self.e, self.sf - o_phi = PhysicsSphericalDifferential(1*u.arcsec, 0*u.arcsec, 0*u.kpc) + o_phi = PhysicsSphericalDifferential(1 * u.arcsec, 0 * u.arcsec, 0 * u.kpc) o_phic = o_phi.to_cartesian(base=s) o_phi2 = PhysicsSphericalDifferential.from_cartesian(o_phic, base=s) - assert_quantity_allclose(o_phi.d_phi, o_phi2.d_phi, atol=1.*u.narcsec) - assert_quantity_allclose(o_phi.d_theta, o_phi2.d_theta, - atol=1.*u.narcsec) - assert_quantity_allclose(o_phi.d_r, o_phi2.d_r, atol=1.*u.npc) + assert_quantity_allclose(o_phi.d_phi, o_phi2.d_phi, atol=1.0 * u.narcsec) + assert_quantity_allclose(o_phi.d_theta, o_phi2.d_theta, atol=1.0 * u.narcsec) + assert_quantity_allclose(o_phi.d_r, o_phi2.d_r, atol=1.0 * u.npc) # simple check by hand for first element. - assert_quantity_allclose(o_phic[0].xyz, - [0., np.pi/180./3600., 0.]*u.kpc, - atol=1.*u.npc) + assert_quantity_allclose( + o_phic[0].xyz, [0.0, np.pi / 180.0 / 3600.0, 0.0] * u.kpc, atol=1.0 * u.npc + ) # check all using unit vectors and scale factors. - s_phi = s + 1.*u.arcsec * sf['phi'] * e['phi'] - assert_representation_allclose(o_phic, s_phi - s, atol=1e-10*u.kpc) + s_phi = s + 1.0 * u.arcsec * sf["phi"] * e["phi"] + assert_representation_allclose(o_phic, s_phi - s, atol=1e-10 * u.kpc) - o_theta = PhysicsSphericalDifferential(0*u.arcsec, 1*u.arcsec, 0*u.kpc) + o_theta = PhysicsSphericalDifferential(0 * u.arcsec, 1 * u.arcsec, 0 * u.kpc) o_thetac = o_theta.to_cartesian(base=s) - assert_quantity_allclose(o_thetac[0].xyz, - [0., 0., -np.pi/180./3600.]*u.kpc, - atol=1.*u.npc) - s_theta = s + 1.*u.arcsec * sf['theta'] * e['theta'] - assert_representation_allclose(o_thetac, s_theta - s, atol=1e-10*u.kpc) + assert_quantity_allclose( + o_thetac[0].xyz, + [0.0, 0.0, -np.pi / 180.0 / 3600.0] * u.kpc, + atol=1.0 * u.npc, + ) + s_theta = s + 1.0 * u.arcsec * sf["theta"] * e["theta"] + assert_representation_allclose(o_thetac, s_theta - s, atol=1e-10 * u.kpc) s_theta2 = s + o_theta - assert_representation_allclose(s_theta2, s_theta, atol=1e-10*u.kpc) + assert_representation_allclose(s_theta2, s_theta, atol=1e-10 * u.kpc) - o_r = PhysicsSphericalDifferential(0*u.arcsec, 0*u.arcsec, 1*u.mpc) + o_r = PhysicsSphericalDifferential(0 * u.arcsec, 0 * u.arcsec, 1 * u.mpc) o_rc = o_r.to_cartesian(base=s) - assert_quantity_allclose(o_rc[0].xyz, [1e-6, 0., 0.]*u.kpc, - atol=1.*u.npc) - s_r = s + 1.*u.mpc * sf['r'] * e['r'] - assert_representation_allclose(o_rc, s_r - s, atol=1e-10*u.kpc) + assert_quantity_allclose( + o_rc[0].xyz, [1e-6, 0.0, 0.0] * u.kpc, atol=1.0 * u.npc + ) + s_r = s + 1.0 * u.mpc * sf["r"] * e["r"] + assert_representation_allclose(o_rc, s_r - s, atol=1e-10 * u.kpc) s_r2 = s + o_r assert_representation_allclose(s_r2, s_r) def test_differential_init_errors(self): with pytest.raises(u.UnitsError): - PhysicsSphericalDifferential(1.*u.arcsec, 0., 0.) + PhysicsSphericalDifferential(1.0 * u.arcsec, 0.0, 0.0) -class TestCylindricalDifferential(): +class TestCylindricalDifferential: """Test copied from SphericalDifferential, so less extensive.""" def setup_method(self): - s = CylindricalRepresentation(rho=[1, 2, 3] * u.kpc, - phi=[0., 90., 315.] * u.deg, - z=[3, 2, 1] * u.kpc) + s = CylindricalRepresentation( + rho=[1, 2, 3] * u.kpc, phi=[0.0, 90.0, 315.0] * u.deg, z=[3, 2, 1] * u.kpc + ) self.s = s self.e = s.unit_vectors() self.sf = s.scale_factors() def test_name(self): - assert CylindricalDifferential.get_name() == 'cylindrical' + assert CylindricalDifferential.get_name() == "cylindrical" assert CylindricalDifferential.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self): s, e, sf = self.s, self.e, self.sf - o_rho = CylindricalDifferential(1.*u.mpc, 0.*u.arcsec, 0.*u.kpc) + o_rho = CylindricalDifferential(1.0 * u.mpc, 0.0 * u.arcsec, 0.0 * u.kpc) o_rhoc = o_rho.to_cartesian(base=s) - assert_quantity_allclose(o_rhoc[0].xyz, [1.e-6, 0., 0.]*u.kpc) - s_rho = s + 1.*u.mpc * sf['rho'] * e['rho'] - assert_representation_allclose(o_rhoc, s_rho - s, atol=1e-10*u.kpc) + assert_quantity_allclose(o_rhoc[0].xyz, [1.0e-6, 0.0, 0.0] * u.kpc) + s_rho = s + 1.0 * u.mpc * sf["rho"] * e["rho"] + assert_representation_allclose(o_rhoc, s_rho - s, atol=1e-10 * u.kpc) s_rho2 = s + o_rho assert_representation_allclose(s_rho2, s_rho) - o_phi = CylindricalDifferential(0.*u.kpc, 1.*u.arcsec, 0.*u.kpc) + o_phi = CylindricalDifferential(0.0 * u.kpc, 1.0 * u.arcsec, 0.0 * u.kpc) o_phic = o_phi.to_cartesian(base=s) o_phi2 = CylindricalDifferential.from_cartesian(o_phic, base=s) - assert_quantity_allclose(o_phi.d_rho, o_phi2.d_rho, atol=1.*u.npc) - assert_quantity_allclose(o_phi.d_phi, o_phi2.d_phi, atol=1.*u.narcsec) - assert_quantity_allclose(o_phi.d_z, o_phi2.d_z, atol=1.*u.npc) + assert_quantity_allclose(o_phi.d_rho, o_phi2.d_rho, atol=1.0 * u.npc) + assert_quantity_allclose(o_phi.d_phi, o_phi2.d_phi, atol=1.0 * u.narcsec) + assert_quantity_allclose(o_phi.d_z, o_phi2.d_z, atol=1.0 * u.npc) # simple check by hand for first element. - assert_quantity_allclose(o_phic[0].xyz, - [0., np.pi/180./3600., 0.]*u.kpc) + assert_quantity_allclose( + o_phic[0].xyz, [0.0, np.pi / 180.0 / 3600.0, 0.0] * u.kpc + ) # check all using unit vectors and scale factors. - s_phi = s + 1.*u.arcsec * sf['phi'] * e['phi'] - assert_representation_allclose(o_phic, s_phi - s, atol=1e-10*u.kpc) + s_phi = s + 1.0 * u.arcsec * sf["phi"] * e["phi"] + assert_representation_allclose(o_phic, s_phi - s, atol=1e-10 * u.kpc) - o_z = CylindricalDifferential(0.*u.kpc, 0.*u.arcsec, 1.*u.mpc) + o_z = CylindricalDifferential(0.0 * u.kpc, 0.0 * u.arcsec, 1.0 * u.mpc) o_zc = o_z.to_cartesian(base=s) - assert_quantity_allclose(o_zc[0].xyz, [0., 0., 1.e-6]*u.kpc) - s_z = s + 1.*u.mpc * sf['z'] * e['z'] - assert_representation_allclose(o_zc, s_z - s, atol=1e-10*u.kpc) + assert_quantity_allclose(o_zc[0].xyz, [0.0, 0.0, 1.0e-6] * u.kpc) + s_z = s + 1.0 * u.mpc * sf["z"] * e["z"] + assert_representation_allclose(o_zc, s_z - s, atol=1e-10 * u.kpc) s_z2 = s + o_z assert_representation_allclose(s_z2, s_z) def test_differential_init_errors(self): with pytest.raises(u.UnitsError): - CylindricalDifferential(1.*u.pc, 1.*u.arcsec, 3.*u.km/u.s) + CylindricalDifferential(1.0 * u.pc, 1.0 * u.arcsec, 3.0 * u.km / u.s) -class TestCartesianDifferential(): +class TestCartesianDifferential: """Test copied from SphericalDifferential, so less extensive.""" def setup_method(self): - s = CartesianRepresentation(x=[1, 2, 3] * u.kpc, - y=[2, 3, 1] * u.kpc, - z=[3, 1, 2] * u.kpc) + s = CartesianRepresentation( + x=[1, 2, 3] * u.kpc, y=[2, 3, 1] * u.kpc, z=[3, 1, 2] * u.kpc + ) self.s = s self.e = s.unit_vectors() self.sf = s.scale_factors() def test_name(self): - assert CartesianDifferential.get_name() == 'cartesian' + assert CartesianDifferential.get_name() == "cartesian" assert CartesianDifferential.get_name() in DIFFERENTIAL_CLASSES def test_simple_differentials(self): s, e, sf = self.s, self.e, self.sf for d, differential in ( # test different inits while we're at it. - ('x', CartesianDifferential(1.*u.pc, 0.*u.pc, 0.*u.pc)), - ('y', CartesianDifferential([0., 1., 0.], unit=u.pc)), - ('z', CartesianDifferential(np.array([[0., 0., 1.]]) * u.pc, - xyz_axis=1))): + ("x", CartesianDifferential(1.0 * u.pc, 0.0 * u.pc, 0.0 * u.pc)), + ("y", CartesianDifferential([0.0, 1.0, 0.0], unit=u.pc)), + ( + "z", + CartesianDifferential(np.array([[0.0, 0.0, 1.0]]) * u.pc, xyz_axis=1), + ), + ): o_c = differential.to_cartesian(base=s) o_c2 = differential.to_cartesian() assert np.all(representation_equal(o_c, o_c2)) - assert all(np.all(getattr(differential, 'd_'+c) == getattr(o_c, c)) - for c in ('x', 'y', 'z')) + assert all( + np.all(getattr(differential, "d_" + c) == getattr(o_c, c)) + for c in ("x", "y", "z") + ) differential2 = CartesianDifferential.from_cartesian(o_c) assert np.all(representation_equal(differential2, differential)) differential3 = CartesianDifferential.from_cartesian(o_c, base=o_c) assert np.all(representation_equal(differential3, differential)) - s_off = s + 1.*u.pc * sf[d] * e[d] - assert_representation_allclose(o_c, s_off - s, atol=1e-10*u.kpc) + s_off = s + 1.0 * u.pc * sf[d] * e[d] + assert_representation_allclose(o_c, s_off - s, atol=1e-10 * u.kpc) s_off2 = s + differential assert_representation_allclose(s_off2, s_off) def test_init_failures(self): with pytest.raises(ValueError): - CartesianDifferential(1.*u.kpc/u.s, 2.*u.kpc) + CartesianDifferential(1.0 * u.kpc / u.s, 2.0 * u.kpc) with pytest.raises(u.UnitsError): - CartesianDifferential(1.*u.kpc/u.s, 2.*u.kpc, 3.*u.kpc) + CartesianDifferential(1.0 * u.kpc / u.s, 2.0 * u.kpc, 3.0 * u.kpc) with pytest.raises(ValueError): - CartesianDifferential(1.*u.kpc, 2.*u.kpc, 3.*u.kpc, xyz_axis=1) + CartesianDifferential(1.0 * u.kpc, 2.0 * u.kpc, 3.0 * u.kpc, xyz_axis=1) -class TestDifferentialConversion(): +class TestDifferentialConversion: def setup_method(self): - self.s = SphericalRepresentation(lon=[0., 6., 21.] * u.hourangle, - lat=[0., -30., 85.] * u.deg, - distance=[1, 2, 3] * u.kpc) + self.s = SphericalRepresentation( + lon=[0.0, 6.0, 21.0] * u.hourangle, + lat=[0.0, -30.0, 85.0] * u.deg, + distance=[1, 2, 3] * u.kpc, + ) - @pytest.mark.parametrize('sd_cls', [SphericalDifferential, - SphericalCosLatDifferential]) + @pytest.mark.parametrize( + "sd_cls", [SphericalDifferential, SphericalCosLatDifferential] + ) def test_represent_as_own_class(self, sd_cls): - so = sd_cls(1.*u.deg, 2.*u.deg, 0.1*u.kpc) + so = sd_cls(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc) so2 = so.represent_as(sd_cls) assert so2 is so def test_represent_other_coslat(self): s = self.s coslat = np.cos(s.lat) - so = SphericalDifferential(1.*u.deg, 2.*u.deg, 0.1*u.kpc) + so = SphericalDifferential(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc) so_coslat = so.represent_as(SphericalCosLatDifferential, base=s) - assert_quantity_allclose(so.d_lon * coslat, - so_coslat.d_lon_coslat) + assert_quantity_allclose(so.d_lon * coslat, so_coslat.d_lon_coslat) so2 = so_coslat.represent_as(SphericalDifferential, base=s) assert np.all(representation_equal(so2, so)) so3 = SphericalDifferential.from_representation(so_coslat, base=s) @@ -1059,26 +1150,30 @@ def test_represent_other_coslat(self): us = s.represent_as(UnitSphericalRepresentation) uo = so.represent_as(UnitSphericalDifferential) uo_coslat = so.represent_as(UnitSphericalCosLatDifferential, base=s) - assert_quantity_allclose(uo.d_lon * coslat, - uo_coslat.d_lon_coslat) + assert_quantity_allclose(uo.d_lon * coslat, uo_coslat.d_lon_coslat) uo2 = uo_coslat.represent_as(UnitSphericalDifferential, base=us) assert np.all(representation_equal(uo2, uo)) uo3 = UnitSphericalDifferential.from_representation(uo_coslat, base=us) assert np.all(representation_equal(uo3, uo)) - uo_coslat2 = UnitSphericalCosLatDifferential.from_representation( - uo, base=us) + uo_coslat2 = UnitSphericalCosLatDifferential.from_representation(uo, base=us) assert np.all(representation_equal(uo_coslat2, uo_coslat)) uo_coslat3 = uo.represent_as(UnitSphericalCosLatDifferential, base=us) assert np.all(representation_equal(uo_coslat3, uo_coslat)) - @pytest.mark.parametrize('sd_cls', [SphericalDifferential, - SphericalCosLatDifferential]) - @pytest.mark.parametrize('r_cls', (SphericalRepresentation, - UnitSphericalRepresentation, - PhysicsSphericalRepresentation, - CylindricalRepresentation)) + @pytest.mark.parametrize( + "sd_cls", [SphericalDifferential, SphericalCosLatDifferential] + ) + @pytest.mark.parametrize( + "r_cls", + ( + SphericalRepresentation, + UnitSphericalRepresentation, + PhysicsSphericalRepresentation, + CylindricalRepresentation, + ), + ) def test_represent_regular_class(self, sd_cls, r_cls): - so = sd_cls(1.*u.deg, 2.*u.deg, 0.1*u.kpc) + so = sd_cls(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc) r = so.represent_as(r_cls, base=self.s) c = so.to_cartesian(self.s) r_check = c.represent_as(r_cls) @@ -1087,8 +1182,9 @@ def test_represent_regular_class(self, sd_cls, r_cls): so3 = sd_cls.from_cartesian(r.to_cartesian(), self.s) assert np.all(representation_equal(so2, so3)) - @pytest.mark.parametrize('sd_cls', [SphericalDifferential, - SphericalCosLatDifferential]) + @pytest.mark.parametrize( + "sd_cls", [SphericalDifferential, SphericalCosLatDifferential] + ) def test_convert_physics(self, sd_cls): # Conversion needs no base for SphericalDifferential, but does # need one (to get the latitude) for SphericalCosLatDifferential. @@ -1101,7 +1197,7 @@ def test_convert_physics(self, sd_cls): base_u = base_s.represent_as(UnitSphericalRepresentation) base_p = base_s.represent_as(PhysicsSphericalRepresentation) - so = sd_cls(1.*u.deg, 2.*u.deg, 0.1*u.kpc) + so = sd_cls(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc) po = so.represent_as(PhysicsSphericalDifferential, base=base_s) so2 = sd_cls.from_representation(po, base=base_s) assert_differential_allclose(so, so2) @@ -1116,8 +1212,9 @@ def test_convert_physics(self, sd_cls): cpo = po.to_cartesian(p[1]) assert_representation_allclose(cso, cpo) assert_representation_allclose(s[1] + so, p[1] + po) - po2 = so.represent_as(PhysicsSphericalDifferential, - base=None if base_s is None else s) + po2 = so.represent_as( + PhysicsSphericalDifferential, base=None if base_s is None else s + ) assert_representation_allclose(s + so, p + po2) suo = usd_cls.from_representation(so) @@ -1137,54 +1234,57 @@ def test_convert_physics(self, sd_cls): assert representation_equal(pro, pro2) @pytest.mark.parametrize( - ('sd_cls', 'usd_cls'), - [(SphericalDifferential, UnitSphericalDifferential), - (SphericalCosLatDifferential, UnitSphericalCosLatDifferential)]) + ("sd_cls", "usd_cls"), + [ + (SphericalDifferential, UnitSphericalDifferential), + (SphericalCosLatDifferential, UnitSphericalCosLatDifferential), + ], + ) def test_convert_unit_spherical_radial(self, sd_cls, usd_cls): s = self.s us = s.represent_as(UnitSphericalRepresentation) rs = s.represent_as(RadialRepresentation) assert_representation_allclose(rs * us, s) - uo = usd_cls(2.*u.deg, 1.*u.deg) + uo = usd_cls(2.0 * u.deg, 1.0 * u.deg) so = uo.represent_as(sd_cls, base=s) - assert_quantity_allclose(so.d_distance, 0.*u.kpc, atol=1.*u.npc) + assert_quantity_allclose(so.d_distance, 0.0 * u.kpc, atol=1.0 * u.npc) uo2 = so.represent_as(usd_cls) - assert_representation_allclose(uo.to_cartesian(us), - uo2.to_cartesian(us)) - so1 = sd_cls(2.*u.deg, 1.*u.deg, 5.*u.pc) + assert_representation_allclose(uo.to_cartesian(us), uo2.to_cartesian(us)) + so1 = sd_cls(2.0 * u.deg, 1.0 * u.deg, 5.0 * u.pc) uo_r = so1.represent_as(usd_cls) ro_r = so1.represent_as(RadialDifferential) assert np.all(representation_equal(uo_r, uo)) - assert np.all(representation_equal(ro_r, RadialDifferential(5.*u.pc))) + assert np.all(representation_equal(ro_r, RadialDifferential(5.0 * u.pc))) - @pytest.mark.parametrize('sd_cls', [SphericalDifferential, - SphericalCosLatDifferential]) + @pytest.mark.parametrize( + "sd_cls", [SphericalDifferential, SphericalCosLatDifferential] + ) def test_convert_cylindrial(self, sd_cls): s = self.s - so = sd_cls(1.*u.deg, 2.*u.deg, 0.1*u.kpc) + so = sd_cls(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc) cyo = so.represent_as(CylindricalDifferential, base=s) cy = s.represent_as(CylindricalRepresentation) so1 = cyo.represent_as(sd_cls, base=cy) - assert_representation_allclose(so.to_cartesian(s), - so1.to_cartesian(s)) + assert_representation_allclose(so.to_cartesian(s), so1.to_cartesian(s)) cyo2 = CylindricalDifferential.from_representation(so, base=cy) - assert_representation_allclose(cyo2.to_cartesian(base=cy), - cyo.to_cartesian(base=cy)) + assert_representation_allclose( + cyo2.to_cartesian(base=cy), cyo.to_cartesian(base=cy) + ) so2 = sd_cls.from_representation(cyo2, base=s) - assert_representation_allclose(so.to_cartesian(s), - so2.to_cartesian(s)) + assert_representation_allclose(so.to_cartesian(s), so2.to_cartesian(s)) - @pytest.mark.parametrize('sd_cls', [SphericalDifferential, - SphericalCosLatDifferential]) + @pytest.mark.parametrize( + "sd_cls", [SphericalDifferential, SphericalCosLatDifferential] + ) def test_combinations(self, sd_cls): if sd_cls is SphericalDifferential: - uo = UnitSphericalDifferential(2.*u.deg, 1.*u.deg) + uo = UnitSphericalDifferential(2.0 * u.deg, 1.0 * u.deg) uo_d_lon = uo.d_lon else: - uo = UnitSphericalCosLatDifferential(2.*u.deg, 1.*u.deg) + uo = UnitSphericalCosLatDifferential(2.0 * u.deg, 1.0 * u.deg) uo_d_lon = uo.d_lon_coslat - ro = RadialDifferential(1.*u.mpc) + ro = RadialDifferential(1.0 * u.mpc) so1 = uo + ro so1c = sd_cls(uo_d_lon, uo.d_lat, ro.d_distance) assert np.all(representation_equal(so1, so1c)) @@ -1193,26 +1293,31 @@ def test_combinations(self, sd_cls): so2c = sd_cls(uo_d_lon, uo.d_lat, -ro.d_distance) assert np.all(representation_equal(so2, so2c)) so3 = so2 + ro - so3c = sd_cls(uo_d_lon, uo.d_lat, 0.*u.kpc) + so3c = sd_cls(uo_d_lon, uo.d_lat, 0.0 * u.kpc) assert np.all(representation_equal(so3, so3c)) so4 = so1 + ro - so4c = sd_cls(uo_d_lon, uo.d_lat, 2*ro.d_distance) + so4c = sd_cls(uo_d_lon, uo.d_lat, 2 * ro.d_distance) assert np.all(representation_equal(so4, so4c)) so5 = so1 - uo - so5c = sd_cls(0*u.deg, 0.*u.deg, ro.d_distance) + so5c = sd_cls(0 * u.deg, 0.0 * u.deg, ro.d_distance) assert np.all(representation_equal(so5, so5c)) - assert_representation_allclose(self.s + (uo+ro), self.s+so1) - - -@pytest.mark.parametrize('op,args', [ - (operator.neg, ()), - (operator.pos, ()), - (operator.mul, (-8.,)), - (operator.truediv, ([4., 8.]*u.s,))], scope='class') + assert_representation_allclose(self.s + (uo + ro), self.s + so1) + + +@pytest.mark.parametrize( + "op,args", + [ + (operator.neg, ()), + (operator.pos, ()), + (operator.mul, (-8.0,)), + (operator.truediv, ([4.0, 8.0] * u.s,)), + ], + scope="class", +) class TestArithmeticWithDifferentials: def setup_class(self): - self.cr = CartesianRepresentation([1, 2, 3]*u.kpc) - self.cd = CartesianDifferential([.1, -.2, .3]*u.km/u.s) + self.cr = CartesianRepresentation([1, 2, 3] * u.kpc) + self.cd = CartesianDifferential([0.1, -0.2, 0.3] * u.km / u.s) self.c = self.cr.with_differentials(self.cd) def test_operation_cartesian(self, op, args): @@ -1221,58 +1326,70 @@ def test_operation_cartesian(self, op, args): assert np.all(ncr == expected) def test_operation_radial(self, op, args): - rep = self.c.represent_as(RadialRepresentation, {'s': RadialDifferential}) + rep = self.c.represent_as(RadialRepresentation, {"s": RadialDifferential}) result = op(rep, *args) expected_distance = op(self.cr.norm(), *args) - expected_rv = op((self.cr/self.cr.norm()).dot(self.cd), *args) + expected_rv = op((self.cr / self.cr.norm()).dot(self.cd), *args) assert u.allclose(result.distance, expected_distance) - assert u.allclose(result.differentials['s'].d_distance, expected_rv) + assert u.allclose(result.differentials["s"].d_distance, expected_rv) - @pytest.mark.parametrize('diff_cls', [ - SphericalDifferential, - SphericalCosLatDifferential, - PhysicsSphericalDifferential, - CylindricalDifferential]) + @pytest.mark.parametrize( + "diff_cls", + [ + SphericalDifferential, + SphericalCosLatDifferential, + PhysicsSphericalDifferential, + CylindricalDifferential, + ], + ) def test_operation_other(self, diff_cls, op, args): rep_cls = diff_cls.base_representation - rep = self.c.represent_as(rep_cls, {'s': diff_cls}) + rep = self.c.represent_as(rep_cls, {"s": diff_cls}) result = op(rep, *args) expected_c = op(self.c, *args) - expected = expected_c.represent_as(rep_cls, {'s': diff_cls}) + expected = expected_c.represent_as(rep_cls, {"s": diff_cls}) # Check that we match in the representation itself. assert_representation_allclose(result, expected) - assert_differential_allclose(result.differentials['s'], - expected.differentials['s']) + assert_differential_allclose( + result.differentials["s"], expected.differentials["s"] + ) # Check that we compare correctly in cartesian as well, just to be sure. - result_c = result.represent_as(CartesianRepresentation, - {'s': CartesianDifferential}) + result_c = result.represent_as( + CartesianRepresentation, {"s": CartesianDifferential} + ) assert_representation_allclose(result_c, expected_c) - assert_differential_allclose(result_c.differentials['s'], - expected_c.differentials['s']) + assert_differential_allclose( + result_c.differentials["s"], expected_c.differentials["s"] + ) - @pytest.mark.parametrize('rep_cls', [ - SphericalRepresentation, - PhysicsSphericalRepresentation, - CylindricalRepresentation]) + @pytest.mark.parametrize( + "rep_cls", + [ + SphericalRepresentation, + PhysicsSphericalRepresentation, + CylindricalRepresentation, + ], + ) def test_operation_cartesian_differential(self, rep_cls, op, args): - rep = self.c.represent_as(rep_cls, {'s': CartesianDifferential}) + rep = self.c.represent_as(rep_cls, {"s": CartesianDifferential}) result = op(rep, *args) expected_c = op(self.c, *args) - expected = expected_c.represent_as(rep_cls, {'s': CartesianDifferential}) + expected = expected_c.represent_as(rep_cls, {"s": CartesianDifferential}) # Check that we match in the representation itself. assert_representation_allclose(result, expected) - assert_differential_allclose(result.differentials['s'], - expected.differentials['s']) + assert_differential_allclose( + result.differentials["s"], expected.differentials["s"] + ) - @pytest.mark.parametrize('diff_cls', [ - UnitSphericalDifferential, - UnitSphericalCosLatDifferential]) + @pytest.mark.parametrize( + "diff_cls", [UnitSphericalDifferential, UnitSphericalCosLatDifferential] + ) def test_operation_unit_spherical(self, diff_cls, op, args): rep_cls = diff_cls.base_representation - rep = self.c.represent_as(rep_cls, {'s': diff_cls}) + rep = self.c.represent_as(rep_cls, {"s": diff_cls}) result = op(rep, *args) if op not in (operator.neg, operator.pos): @@ -1281,58 +1398,75 @@ def test_operation_unit_spherical(self, diff_cls, op, args): expected_cls = rep_cls assert type(result) is expected_cls - assert type(result.differentials['s']) is diff_cls + assert type(result.differentials["s"]) is diff_cls # Have lost information, so unlike above we convert our initial # unit-spherical back to Cartesian, and check that applying # the operation on that cartesian representation gives the same result. # We do not compare the output directly, since for multiplication # and division there will be sign flips in the spherical distance. - expected_c = op(rep.represent_as(CartesianRepresentation, - {'s': CartesianDifferential}), *args) - result_c = result.represent_as(CartesianRepresentation, - {'s': CartesianDifferential}) + expected_c = op( + rep.represent_as(CartesianRepresentation, {"s": CartesianDifferential}), + *args + ) + result_c = result.represent_as( + CartesianRepresentation, {"s": CartesianDifferential} + ) assert_representation_allclose(result_c, expected_c) - assert_differential_allclose(result_c.differentials['s'], - expected_c.differentials['s']) + assert_differential_allclose( + result_c.differentials["s"], expected_c.differentials["s"] + ) - @pytest.mark.parametrize('diff_cls', [ - RadialDifferential, - UnitSphericalDifferential, - UnitSphericalCosLatDifferential]) + @pytest.mark.parametrize( + "diff_cls", + [ + RadialDifferential, + UnitSphericalDifferential, + UnitSphericalCosLatDifferential, + ], + ) def test_operation_spherical_with_rv_or_pm(self, diff_cls, op, args): - rep = self.c.represent_as(SphericalRepresentation, {'s': diff_cls}) + rep = self.c.represent_as(SphericalRepresentation, {"s": diff_cls}) result = op(rep, *args) assert type(result) is SphericalRepresentation - assert type(result.differentials['s']) is diff_cls - - expected_c = op(rep.represent_as(CartesianRepresentation, - {'s': CartesianDifferential}), *args) - result_c = result.represent_as(CartesianRepresentation, - {'s': CartesianDifferential}) + assert type(result.differentials["s"]) is diff_cls + + expected_c = op( + rep.represent_as(CartesianRepresentation, {"s": CartesianDifferential}), + *args + ) + result_c = result.represent_as( + CartesianRepresentation, {"s": CartesianDifferential} + ) assert_representation_allclose(result_c, expected_c) - assert_differential_allclose(result_c.differentials['s'], - expected_c.differentials['s']) + assert_differential_allclose( + result_c.differentials["s"], expected_c.differentials["s"] + ) -@pytest.mark.parametrize('op,args', [ - (operator.neg, ()), - (operator.mul, (10.,))]) +@pytest.mark.parametrize("op,args", [(operator.neg, ()), (operator.mul, (10.0,))]) def test_operation_unitspherical_with_rv_fails(op, args): rep = UnitSphericalRepresentation( - 0*u.deg, 0*u.deg, differentials={'s': RadialDifferential(10*u.km/u.s)}) - with pytest.raises(ValueError, match='unit key'): + 0 * u.deg, 0 * u.deg, differentials={"s": RadialDifferential(10 * u.km / u.s)} + ) + with pytest.raises(ValueError, match="unit key"): op(rep, *args) -@pytest.mark.parametrize('rep,dif', [ - [CartesianRepresentation([1, 2, 3]*u.kpc), - CartesianDifferential([.1, .2, .3]*u.km/u.s)], - [SphericalRepresentation(90*u.deg, 0.*u.deg, 14.*u.kpc), - SphericalDifferential(1.*u.deg, 2.*u.deg, 0.1*u.kpc)] -]) +@pytest.mark.parametrize( + "rep,dif", + [ + [ + CartesianRepresentation([1, 2, 3] * u.kpc), + CartesianDifferential([0.1, 0.2, 0.3] * u.km / u.s), + ], + [ + SphericalRepresentation(90 * u.deg, 0.0 * u.deg, 14.0 * u.kpc), + SphericalDifferential(1.0 * u.deg, 2.0 * u.deg, 0.1 * u.kpc), + ], + ], +) def test_arithmetic_with_differentials_fail(rep, dif): - rep = rep.with_differentials(dif) with pytest.raises(TypeError): diff --git a/astropy/coordinates/tests/test_representation_methods.py b/astropy/coordinates/tests/test_representation_methods.py index 68521c752d8..2a0d11c9468 100644 --- a/astropy/coordinates/tests/test_representation_methods.py +++ b/astropy/coordinates/tests/test_representation_methods.py @@ -16,15 +16,14 @@ from .test_representation import representation_equal -@pytest.fixture(params=[True, False] if ARRAY_FUNCTION_ENABLED - else [True]) +@pytest.fixture(params=[True, False] if ARRAY_FUNCTION_ENABLED else [True]) def method(request): return request.param needs_array_function = pytest.mark.xfail( - not ARRAY_FUNCTION_ENABLED, - reason="Needs __array_function__ support") + not ARRAY_FUNCTION_ENABLED, reason="Needs __array_function__ support" +) class ShapeSetup: @@ -46,18 +45,21 @@ def setup_class(cls): lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc, - copy=False) + copy=False, + ) cls.diff = SphericalDifferential( - d_lon=np.ones(cls.s0.shape)*u.mas/u.yr, - d_lat=np.ones(cls.s0.shape)*u.mas/u.yr, - d_distance=np.ones(cls.s0.shape)*u.km/u.s, - copy=False) + d_lon=np.ones(cls.s0.shape) * u.mas / u.yr, + d_lat=np.ones(cls.s0.shape) * u.mas / u.yr, + d_distance=np.ones(cls.s0.shape) * u.km / u.s, + copy=False, + ) cls.s0 = cls.s0.with_differentials(cls.diff) # With unequal arrays -> these will be broadcasted. - cls.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, - differentials=cls.diff, copy=False) + cls.s1 = SphericalRepresentation( + lon[:, np.newaxis], lat, 1.0 * u.kpc, differentials=cls.diff, copy=False + ) # For completeness on some tests, also a cartesian one cls.c0 = cls.s0.to_cartesian() @@ -82,7 +84,7 @@ def test_ravel(self, method): assert np.may_share_memory(s0_ravel.lon, self.s0.lon) assert np.may_share_memory(s0_ravel.lat, self.s0.lat) assert np.may_share_memory(s0_ravel.distance, self.s0.distance) - assert s0_ravel.differentials['s'].shape == (self.s0.size,) + assert s0_ravel.differentials["s"].shape == (self.s0.size,) # Since s1 was broadcast, ravel needs to make a copy. if method: @@ -91,7 +93,7 @@ def test_ravel(self, method): s1_ravel = np.ravel(self.s1) assert type(s1_ravel) is type(self.s1) assert s1_ravel.shape == (self.s1.size,) - assert s1_ravel.differentials['s'].shape == (self.s1.size,) + assert s1_ravel.differentials["s"].shape == (self.s1.size,) assert np.all(s1_ravel.lon == self.s1.lon.ravel()) assert not np.may_share_memory(s1_ravel.lat, self.s1.lat) @@ -100,7 +102,7 @@ def test_copy(self, method): s0_copy = self.s0.copy() else: s0_copy = np.copy(self.s0) - s0_copy_diff = s0_copy.differentials['s'] + s0_copy_diff = s0_copy.differentials["s"] assert s0_copy.shape == self.s0.shape assert np.all(s0_copy.lon == self.s0.lon) assert np.all(s0_copy.lat == self.s0.lat) @@ -111,7 +113,7 @@ def test_copy(self, method): def test_flatten(self): s0_flatten = self.s0.flatten() - s0_diff = s0_flatten.differentials['s'] + s0_diff = s0_flatten.differentials["s"] assert s0_flatten.shape == (self.s0.size,) assert s0_diff.shape == (self.s0.size,) assert np.all(s0_flatten.lon == self.s0.lon.flatten()) @@ -128,7 +130,7 @@ def test_flatten(self): def test_transpose(self): s0_transpose = self.s0.transpose() - s0_diff = s0_transpose.differentials['s'] + s0_diff = s0_transpose.differentials["s"] assert s0_transpose.shape == (7, 6) assert s0_diff.shape == s0_transpose.shape assert np.all(s0_transpose.lon == self.s0.lon.transpose()) @@ -137,7 +139,7 @@ def test_transpose(self): assert np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) s1_transpose = self.s1.transpose() - s1_diff = s1_transpose.differentials['s'] + s1_diff = s1_transpose.differentials["s"] assert s1_transpose.shape == (7, 6) assert s1_diff.shape == s1_transpose.shape assert np.all(s1_transpose.lat == self.s1.lat.transpose()) @@ -154,7 +156,7 @@ def test_transpose(self): def test_diagonal(self): s0_diagonal = self.s0.diagonal() - s0_diff = s0_diagonal.differentials['s'] + s0_diff = s0_diagonal.differentials["s"] assert s0_diagonal.shape == (6,) assert s0_diff.shape == s0_diagonal.shape assert np.all(s0_diagonal.lat == self.s0.lat.diagonal()) @@ -167,7 +169,7 @@ def test_swapaxes(self, method): s1_swapaxes = self.s1.swapaxes(0, 1) else: s1_swapaxes = np.swapaxes(self.s1, 0, 1) - s1_diff = s1_swapaxes.differentials['s'] + s1_diff = s1_swapaxes.differentials["s"] assert s1_swapaxes.shape == (7, 6) assert s1_diff.shape == s1_swapaxes.shape assert np.all(s1_swapaxes.lat == self.s1.lat.swapaxes(0, 1)) @@ -177,7 +179,7 @@ def test_swapaxes(self, method): def test_reshape(self): s0_reshape = self.s0.reshape(2, 3, 7) - s0_diff = s0_reshape.differentials['s'] + s0_diff = s0_reshape.differentials["s"] assert s0_reshape.shape == (2, 3, 7) assert s0_diff.shape == s0_reshape.shape assert np.all(s0_reshape.lon == self.s0.lon.reshape(2, 3, 7)) @@ -188,7 +190,7 @@ def test_reshape(self): assert np.may_share_memory(s0_reshape.distance, self.s0.distance) s1_reshape = self.s1.reshape(3, 2, 7) - s1_diff = s1_reshape.differentials['s'] + s1_diff = s1_reshape.differentials["s"] assert s1_reshape.shape == (3, 2, 7) assert s1_diff.shape == s1_reshape.shape assert np.all(s1_reshape.lat == self.s1.lat.reshape(3, 2, 7)) @@ -206,7 +208,7 @@ def test_reshape(self): def test_squeeze(self): s0_squeeze = self.s0.reshape(3, 1, 2, 1, 7).squeeze() - s0_diff = s0_squeeze.differentials['s'] + s0_diff = s0_squeeze.differentials["s"] assert s0_squeeze.shape == (3, 2, 7) assert s0_diff.shape == s0_squeeze.shape assert np.all(s0_squeeze.lat == self.s0.lat.reshape(3, 2, 7)) @@ -215,7 +217,7 @@ def test_squeeze(self): def test_add_dimension(self): s0_adddim = self.s0[:, np.newaxis, :] - s0_diff = s0_adddim.differentials['s'] + s0_diff = s0_adddim.differentials["s"] assert s0_adddim.shape == (6, 1, 7) assert s0_diff.shape == s0_adddim.shape assert np.all(s0_adddim.lon == self.s0.lon[:, np.newaxis, :]) @@ -227,7 +229,7 @@ def test_take(self, method): s0_take = self.s0.take((5, 2)) else: s0_take = np.take(self.s0, (5, 2)) - s0_diff = s0_take.differentials['s'] + s0_diff = s0_take.differentials["s"] assert s0_take.shape == (2,) assert s0_diff.shape == s0_take.shape assert np.all(s0_take.lon == self.s0.lon.take((5, 2))) @@ -235,7 +237,7 @@ def test_take(self, method): def test_broadcast_to_via_apply(self): s0_broadcast = self.s0._apply(np.broadcast_to, (3, 6, 7), subok=True) - s0_diff = s0_broadcast.differentials['s'] + s0_diff = s0_broadcast.differentials["s"] assert type(s0_broadcast) is type(self.s0) assert s0_broadcast.shape == (3, 6, 7) assert s0_diff.shape == s0_broadcast.shape @@ -283,8 +285,9 @@ def test_shape_setting(self): # Finally, a more complicated one that checks that things get reset # properly if it is not the first component that fails. - s2 = SphericalRepresentation(self.s1.lon.copy(), self.s1.lat, - self.s1.distance, copy=False) + s2 = SphericalRepresentation( + self.s1.lon.copy(), self.s1.lat, self.s1.distance, copy=False + ) assert 0 not in s2.lon.strides assert 0 in s2.lat.strides with pytest.raises(AttributeError): @@ -301,7 +304,7 @@ class TestShapeFunctions(ShapeSetup): @needs_array_function def test_broadcast_to(self): s0_broadcast = np.broadcast_to(self.s0, (3, 6, 7)) - s0_diff = s0_broadcast.differentials['s'] + s0_diff = s0_broadcast.differentials["s"] assert type(s0_broadcast) is type(self.s0) assert s0_broadcast.shape == (3, 6, 7) assert s0_diff.shape == s0_broadcast.shape @@ -313,7 +316,7 @@ def test_broadcast_to(self): assert np.may_share_memory(s0_broadcast.distance, self.s0.distance) s1_broadcast = np.broadcast_to(self.s1, shape=(3, 6, 7)) - s1_diff = s1_broadcast.differentials['s'] + s1_diff = s1_broadcast.differentials["s"] assert s1_broadcast.shape == (3, 6, 7) assert s1_diff.shape == s1_broadcast.shape assert np.all(s1_broadcast.lat == self.s1.lat) @@ -332,8 +335,8 @@ def test_broadcast_to(self): sc_broadcast = np.broadcast_to(sc, (3, 6, 7)) assert np.may_share_memory(sc_broadcast.lon, sc.lon) # Can only write to copy, not to broadcast version. - sc.lon[0, 0] = 22. * u.hourangle - assert np.all(sc_broadcast.lon[:, 0, 0] == 22. * u.hourangle) + sc.lon[0, 0] = 22.0 * u.hourangle + assert np.all(sc_broadcast.lon[:, 0, 0] == 22.0 * u.hourangle) @needs_array_function def test_atleast_1d(self): @@ -358,10 +361,8 @@ def test_atleast_3d(self): assert self.s0.ndim == 2 s0_3d, s1_3d = np.atleast_3d(self.s0, self.s1) assert s0_3d.ndim == s1_3d.ndim == 3 - assert np.all(representation_equal(self.s0[:, :, np.newaxis], - s0_3d)) - assert np.all(representation_equal(self.s1[:, :, np.newaxis], - s1_3d)) + assert np.all(representation_equal(self.s0[:, :, np.newaxis], s0_3d)) + assert np.all(representation_equal(self.s1[:, :, np.newaxis], s1_3d)) assert np.may_share_memory(s0_3d.lon, self.s0.lon) def test_move_axis(self): @@ -402,7 +403,7 @@ def test_delete(self): assert np.all(representation_equal(s0d[:2], self.s0[:2])) assert np.all(representation_equal(s0d[2:], self.s0[4:])) - @pytest.mark.parametrize('attribute', ['shape', 'ndim', 'size']) + @pytest.mark.parametrize("attribute", ["shape", "ndim", "size"]) def test_shape_attribute_functions(self, attribute): function = getattr(np, attribute) result = function(self.s0) diff --git a/astropy/coordinates/tests/test_shape_manipulation.py b/astropy/coordinates/tests/test_shape_manipulation.py index 7ec4dd1d531..193c85f400e 100644 --- a/astropy/coordinates/tests/test_shape_manipulation.py +++ b/astropy/coordinates/tests/test_shape_manipulation.py @@ -13,18 +13,17 @@ from astropy.units.quantity_helper.function_helpers import ARRAY_FUNCTION_ENABLED -@pytest.fixture(params=[True, False] if ARRAY_FUNCTION_ENABLED - else [True]) +@pytest.fixture(params=[True, False] if ARRAY_FUNCTION_ENABLED else [True]) def method(request): return request.param needs_array_function = pytest.mark.xfail( - not ARRAY_FUNCTION_ENABLED, - reason="Needs __array_function__ support") + not ARRAY_FUNCTION_ENABLED, reason="Needs __array_function__ support" +) -class TestManipulation(): +class TestManipulation: """Manipulation of Frame shapes. Checking that attributes are manipulated correctly. @@ -38,39 +37,51 @@ def setup_class(cls): lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays, no attributes. - cls.s0 = ICRS(lon[:, np.newaxis] * np.ones(lat.shape), - lat * np.ones(lon.shape)[:, np.newaxis], copy=False) + cls.s0 = ICRS( + lon[:, np.newaxis] * np.ones(lat.shape), + lat * np.ones(lon.shape)[:, np.newaxis], + copy=False, + ) # Make an AltAz frame since that has many types of attributes. # Match one axis with times. - cls.obstime = (Time('2012-01-01') + - np.arange(len(lon))[:, np.newaxis] * u.s) + cls.obstime = Time("2012-01-01") + np.arange(len(lon))[:, np.newaxis] * u.s # And another with location. - cls.location = EarthLocation(20.*u.deg, lat, 100*u.m) + cls.location = EarthLocation(20.0 * u.deg, lat, 100 * u.m) # Ensure we have a quantity scalar. cls.pressure = 1000 * u.hPa # As well as an array. - cls.temperature = np.random.uniform( - 0., 20., size=(lon.size, lat.size)) * u.deg_C - cls.s1 = AltAz(az=lon[:, np.newaxis], alt=lat, - obstime=cls.obstime, - location=cls.location, - pressure=cls.pressure, - temperature=cls.temperature, copy=False) + cls.temperature = ( + np.random.uniform(0.0, 20.0, size=(lon.size, lat.size)) * u.deg_C + ) + cls.s1 = AltAz( + az=lon[:, np.newaxis], + alt=lat, + obstime=cls.obstime, + location=cls.location, + pressure=cls.pressure, + temperature=cls.temperature, + copy=False, + ) # For some tests, also try a GCRS, since that has representation # attributes. We match the second dimension (via the location) - cls.obsgeoloc, cls.obsgeovel = cls.location.get_gcrs_posvel( - cls.obstime[0, 0]) - cls.s2 = GCRS(ra=lon[:, np.newaxis], dec=lat, - obstime=cls.obstime, - obsgeoloc=cls.obsgeoloc, - obsgeovel=cls.obsgeovel, copy=False) + cls.obsgeoloc, cls.obsgeovel = cls.location.get_gcrs_posvel(cls.obstime[0, 0]) + cls.s2 = GCRS( + ra=lon[:, np.newaxis], + dec=lat, + obstime=cls.obstime, + obsgeoloc=cls.obsgeoloc, + obsgeovel=cls.obsgeovel, + copy=False, + ) # For completeness, also some tests on an empty frame. - cls.s3 = GCRS(obstime=cls.obstime, - obsgeoloc=cls.obsgeoloc, - obsgeovel=cls.obsgeovel, copy=False) + cls.s3 = GCRS( + obstime=cls.obstime, + obsgeoloc=cls.obsgeoloc, + obsgeovel=cls.obsgeovel, + copy=False, + ) # And make a SkyCoord - cls.sc = SkyCoord(ra=lon[:, np.newaxis], dec=lat, frame=cls.s3, - copy=False) + cls.sc = SkyCoord(ra=lon[:, np.newaxis], dec=lat, frame=cls.s3, copy=False) def test_getitem0101(self): # We on purpose take a slice with only one element, as for the @@ -136,8 +147,7 @@ def test_ravel(self): assert np.all(s1_ravel.data.lon == self.s1.data.lon.ravel()) assert not np.may_share_memory(s1_ravel.data.lat, self.s1.data.lat) assert np.all(s1_ravel.obstime == self.s1.obstime.ravel()) - assert not np.may_share_memory(s1_ravel.obstime.jd1, - self.s1.obstime.jd1) + assert not np.may_share_memory(s1_ravel.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_ravel.location == self.s1.location.ravel()) assert not np.may_share_memory(s1_ravel.location, self.s1.location) assert np.all(s1_ravel.temperature == self.s1.temperature.ravel()) @@ -148,32 +158,26 @@ def test_ravel(self): assert np.all(s2_ravel.data.lon == self.s2.data.lon.ravel()) assert not np.may_share_memory(s2_ravel.data.lat, self.s2.data.lat) assert np.all(s2_ravel.obstime == self.s2.obstime.ravel()) - assert not np.may_share_memory(s2_ravel.obstime.jd1, - self.s2.obstime.jd1) + assert not np.may_share_memory(s2_ravel.obstime.jd1, self.s2.obstime.jd1) # CartesianRepresentation do not allow direct comparisons, as this is # too tricky to get right in the face of rounding issues. Here, though, # it cannot be an issue, so we compare the xyz quantities. assert np.all(s2_ravel.obsgeoloc.xyz == self.s2.obsgeoloc.ravel().xyz) - assert not np.may_share_memory(s2_ravel.obsgeoloc.x, - self.s2.obsgeoloc.x) + assert not np.may_share_memory(s2_ravel.obsgeoloc.x, self.s2.obsgeoloc.x) s3_ravel = self.s3.ravel() assert s3_ravel.shape == (42,) # cannot use .size on frame w/o data. assert np.all(s3_ravel.obstime == self.s3.obstime.ravel()) - assert not np.may_share_memory(s3_ravel.obstime.jd1, - self.s3.obstime.jd1) + assert not np.may_share_memory(s3_ravel.obstime.jd1, self.s3.obstime.jd1) assert np.all(s3_ravel.obsgeoloc.xyz == self.s3.obsgeoloc.ravel().xyz) - assert not np.may_share_memory(s3_ravel.obsgeoloc.x, - self.s3.obsgeoloc.x) + assert not np.may_share_memory(s3_ravel.obsgeoloc.x, self.s3.obsgeoloc.x) sc_ravel = self.sc.ravel() assert sc_ravel.shape == (self.sc.size,) assert np.all(sc_ravel.data.lon == self.sc.data.lon.ravel()) assert not np.may_share_memory(sc_ravel.data.lat, self.sc.data.lat) assert np.all(sc_ravel.obstime == self.sc.obstime.ravel()) - assert not np.may_share_memory(sc_ravel.obstime.jd1, - self.sc.obstime.jd1) + assert not np.may_share_memory(sc_ravel.obstime.jd1, self.sc.obstime.jd1) assert np.all(sc_ravel.obsgeoloc.xyz == self.sc.obsgeoloc.ravel().xyz) - assert not np.may_share_memory(sc_ravel.obsgeoloc.x, - self.sc.obsgeoloc.x) + assert not np.may_share_memory(sc_ravel.obsgeoloc.x, self.sc.obsgeoloc.x) def test_flatten(self): s0_flatten = self.s0.flatten() @@ -186,13 +190,11 @@ def test_flatten(self): assert np.all(s1_flatten.data.lat == self.s1.data.lat.flatten()) assert not np.may_share_memory(s1_flatten.data.lon, self.s1.data.lat) assert np.all(s1_flatten.obstime == self.s1.obstime.flatten()) - assert not np.may_share_memory(s1_flatten.obstime.jd1, - self.s1.obstime.jd1) + assert not np.may_share_memory(s1_flatten.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_flatten.location == self.s1.location.flatten()) assert not np.may_share_memory(s1_flatten.location, self.s1.location) assert np.all(s1_flatten.temperature == self.s1.temperature.flatten()) - assert not np.may_share_memory(s1_flatten.temperature, - self.s1.temperature) + assert not np.may_share_memory(s1_flatten.temperature, self.s1.temperature) assert s1_flatten.pressure == self.s1.pressure def test_transpose(self): @@ -205,14 +207,11 @@ def test_transpose(self): assert np.all(s1_transpose.data.lat == self.s1.data.lat.transpose()) assert np.may_share_memory(s1_transpose.data.lon, self.s1.data.lon) assert np.all(s1_transpose.obstime == self.s1.obstime.transpose()) - assert np.may_share_memory(s1_transpose.obstime.jd1, - self.s1.obstime.jd1) + assert np.may_share_memory(s1_transpose.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_transpose.location == self.s1.location.transpose()) assert np.may_share_memory(s1_transpose.location, self.s1.location) - assert np.all(s1_transpose.temperature == - self.s1.temperature.transpose()) - assert np.may_share_memory(s1_transpose.temperature, - self.s1.temperature) + assert np.all(s1_transpose.temperature == self.s1.temperature.transpose()) + assert np.may_share_memory(s1_transpose.temperature, self.s1.temperature) assert s1_transpose.pressure == self.s1.pressure # Only one check on T, since it just calls transpose anyway. s1_T = self.s1.T @@ -232,15 +231,12 @@ def test_swapaxes(self): assert np.all(s1_swapaxes.data.lat == self.s1.data.lat.swapaxes(0, 1)) assert np.may_share_memory(s1_swapaxes.data.lat, self.s1.data.lat) assert np.all(s1_swapaxes.obstime == self.s1.obstime.swapaxes(0, 1)) - assert np.may_share_memory(s1_swapaxes.obstime.jd1, - self.s1.obstime.jd1) + assert np.may_share_memory(s1_swapaxes.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_swapaxes.location == self.s1.location.swapaxes(0, 1)) assert s1_swapaxes.location.shape == (7, 6) assert np.may_share_memory(s1_swapaxes.location, self.s1.location) - assert np.all(s1_swapaxes.temperature == - self.s1.temperature.swapaxes(0, 1)) - assert np.may_share_memory(s1_swapaxes.temperature, - self.s1.temperature) + assert np.all(s1_swapaxes.temperature == self.s1.temperature.swapaxes(0, 1)) + assert np.may_share_memory(s1_swapaxes.temperature, self.s1.temperature) assert s1_swapaxes.pressure == self.s1.pressure def test_reshape(self): @@ -255,14 +251,11 @@ def test_reshape(self): assert np.all(s1_reshape.data.lat == self.s1.data.lat.reshape(3, 2, 7)) assert np.may_share_memory(s1_reshape.data.lat, self.s1.data.lat) assert np.all(s1_reshape.obstime == self.s1.obstime.reshape(3, 2, 7)) - assert np.may_share_memory(s1_reshape.obstime.jd1, - self.s1.obstime.jd1) + assert np.may_share_memory(s1_reshape.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_reshape.location == self.s1.location.reshape(3, 2, 7)) assert np.may_share_memory(s1_reshape.location, self.s1.location) - assert np.all(s1_reshape.temperature == - self.s1.temperature.reshape(3, 2, 7)) - assert np.may_share_memory(s1_reshape.temperature, - self.s1.temperature) + assert np.all(s1_reshape.temperature == self.s1.temperature.reshape(3, 2, 7)) + assert np.may_share_memory(s1_reshape.temperature, self.s1.temperature) assert s1_reshape.pressure == self.s1.pressure # For reshape(3, 14), copying is necessary for lon, lat, location, time s1_reshape2 = self.s1.reshape(3, 14) @@ -270,14 +263,11 @@ def test_reshape(self): assert np.all(s1_reshape2.data.lon == self.s1.data.lon.reshape(3, 14)) assert not np.may_share_memory(s1_reshape2.data.lon, self.s1.data.lon) assert np.all(s1_reshape2.obstime == self.s1.obstime.reshape(3, 14)) - assert not np.may_share_memory(s1_reshape2.obstime.jd1, - self.s1.obstime.jd1) + assert not np.may_share_memory(s1_reshape2.obstime.jd1, self.s1.obstime.jd1) assert np.all(s1_reshape2.location == self.s1.location.reshape(3, 14)) assert not np.may_share_memory(s1_reshape2.location, self.s1.location) - assert np.all(s1_reshape2.temperature == - self.s1.temperature.reshape(3, 14)) - assert np.may_share_memory(s1_reshape2.temperature, - self.s1.temperature) + assert np.all(s1_reshape2.temperature == self.s1.temperature.reshape(3, 14)) + assert np.may_share_memory(s1_reshape2.temperature, self.s1.temperature) assert s1_reshape2.pressure == self.s1.pressure s2_reshape = self.s2.reshape(3, 2, 7) assert s2_reshape.shape == (3, 2, 7) @@ -285,15 +275,17 @@ def test_reshape(self): assert np.may_share_memory(s2_reshape.data.lat, self.s2.data.lat) assert np.all(s2_reshape.obstime == self.s2.obstime.reshape(3, 2, 7)) assert np.may_share_memory(s2_reshape.obstime.jd1, self.s2.obstime.jd1) - assert np.all(s2_reshape.obsgeoloc.xyz == - self.s2.obsgeoloc.reshape(3, 2, 7).xyz) + assert np.all( + s2_reshape.obsgeoloc.xyz == self.s2.obsgeoloc.reshape(3, 2, 7).xyz + ) assert np.may_share_memory(s2_reshape.obsgeoloc.x, self.s2.obsgeoloc.x) s3_reshape = self.s3.reshape(3, 2, 7) assert s3_reshape.shape == (3, 2, 7) assert np.all(s3_reshape.obstime == self.s3.obstime.reshape(3, 2, 7)) assert np.may_share_memory(s3_reshape.obstime.jd1, self.s3.obstime.jd1) - assert np.all(s3_reshape.obsgeoloc.xyz == - self.s3.obsgeoloc.reshape(3, 2, 7).xyz) + assert np.all( + s3_reshape.obsgeoloc.xyz == self.s3.obsgeoloc.reshape(3, 2, 7).xyz + ) assert np.may_share_memory(s3_reshape.obsgeoloc.x, self.s3.obsgeoloc.x) sc_reshape = self.sc.reshape(3, 2, 7) assert sc_reshape.shape == (3, 2, 7) @@ -301,22 +293,19 @@ def test_reshape(self): assert np.may_share_memory(sc_reshape.data.lat, self.sc.data.lat) assert np.all(sc_reshape.obstime == self.sc.obstime.reshape(3, 2, 7)) assert np.may_share_memory(sc_reshape.obstime.jd1, self.sc.obstime.jd1) - assert np.all(sc_reshape.obsgeoloc.xyz == - self.sc.obsgeoloc.reshape(3, 2, 7).xyz) + assert np.all( + sc_reshape.obsgeoloc.xyz == self.sc.obsgeoloc.reshape(3, 2, 7).xyz + ) assert np.may_share_memory(sc_reshape.obsgeoloc.x, self.sc.obsgeoloc.x) # For reshape(3, 14), the arrays all need to be copied. sc_reshape2 = self.sc.reshape(3, 14) assert sc_reshape2.shape == (3, 14) assert np.all(sc_reshape2.data.lon == self.sc.data.lon.reshape(3, 14)) - assert not np.may_share_memory(sc_reshape2.data.lat, - self.sc.data.lat) + assert not np.may_share_memory(sc_reshape2.data.lat, self.sc.data.lat) assert np.all(sc_reshape2.obstime == self.sc.obstime.reshape(3, 14)) - assert not np.may_share_memory(sc_reshape2.obstime.jd1, - self.sc.obstime.jd1) - assert np.all(sc_reshape2.obsgeoloc.xyz == - self.sc.obsgeoloc.reshape(3, 14).xyz) - assert not np.may_share_memory(sc_reshape2.obsgeoloc.x, - self.sc.obsgeoloc.x) + assert not np.may_share_memory(sc_reshape2.obstime.jd1, self.sc.obstime.jd1) + assert np.all(sc_reshape2.obsgeoloc.xyz == self.sc.obsgeoloc.reshape(3, 14).xyz) + assert not np.may_share_memory(sc_reshape2.obsgeoloc.x, self.sc.obsgeoloc.x) def test_squeeze(self): s0_squeeze = self.s0.reshape(3, 1, 2, 1, 7).squeeze() diff --git a/astropy/coordinates/tests/test_sites.py b/astropy/coordinates/tests/test_sites.py index e4bc916975b..d55501996e7 100644 --- a/astropy/coordinates/tests/test_sites.py +++ b/astropy/coordinates/tests/test_sites.py @@ -14,72 +14,76 @@ def test_builtin_sites(): reg = get_builtin_sites() - greenwich = reg['greenwich'] + greenwich = reg["greenwich"] lon, lat, el = greenwich.to_geodetic() - assert_quantity_allclose(lon, Longitude('0:0:0', unit=u.deg), - atol=10*u.arcsec) - assert_quantity_allclose(lat, Latitude('51:28:40', unit=u.deg), - atol=1*u.arcsec) - assert_quantity_allclose(el, 46*u.m, atol=1*u.m) + assert_quantity_allclose(lon, Longitude("0:0:0", unit=u.deg), atol=10 * u.arcsec) + assert_quantity_allclose(lat, Latitude("51:28:40", unit=u.deg), atol=1 * u.arcsec) + assert_quantity_allclose(el, 46 * u.m, atol=1 * u.m) names = reg.names - assert 'greenwich' in names - assert 'example_site' in names + assert "greenwich" in names + assert "example_site" in names - with pytest.raises(KeyError) as exc: - reg['nonexistent site'] - assert exc.value.args[0] == "Site 'nonexistent site' not in database. Use the 'names' attribute to see available sites." + with pytest.raises( + KeyError, + match="Site 'nonexistent' not in database. Use the 'names' attribute to see", + ): + reg["nonexistent"] -@pytest.mark.remote_data(source='astropy') +@pytest.mark.remote_data(source="astropy") def test_online_sites(): reg = get_downloaded_sites() - keck = reg['keck'] + keck = reg["keck"] lon, lat, el = keck.to_geodetic() - assert_quantity_allclose(lon, -Longitude('155:28.7', unit=u.deg), - atol=0.001*u.deg) - assert_quantity_allclose(lat, Latitude('19:49.7', unit=u.deg), - atol=0.001*u.deg) - assert_quantity_allclose(el, 4160*u.m, atol=1*u.m) + assert_quantity_allclose( + lon, -Longitude("155:28.7", unit=u.deg), atol=0.001 * u.deg + ) + assert_quantity_allclose(lat, Latitude("19:49.7", unit=u.deg), atol=0.001 * u.deg) + assert_quantity_allclose(el, 4160 * u.m, atol=1 * u.m) names = reg.names - assert 'keck' in names - assert 'ctio' in names + assert "keck" in names + assert "ctio" in names # The JSON file contains `name` and `aliases` for each site, and astropy # should use names from both, but not empty strings [#12721]. - assert '' not in names - assert 'Royal Observatory Greenwich' in names + assert "" not in names + assert "Royal Observatory Greenwich" in names - with pytest.raises(KeyError) as exc: - reg['nonexistent site'] - assert exc.value.args[0] == "Site 'nonexistent site' not in database. Use the 'names' attribute to see available sites." + with pytest.raises( + KeyError, + match="Site 'nonexistent' not in database. Use the 'names' attribute to see", + ): + reg["nonexistent"] - with pytest.raises(KeyError) as exc: - reg['kec'] - assert exc.value.args[0] == "Site 'kec' not in database. Use the 'names' attribute to see available sites. Did you mean one of: 'keck'?'" + with pytest.raises( + KeyError, + match="Site 'kec' not in database. Use the 'names' attribute to see available", + ): + reg["kec"] -@pytest.mark.remote_data(source='astropy') +@pytest.mark.remote_data(source="astropy") # this will *try* the online so we have to make it remote_data, even though it # could fall back on the non-remote version def test_EarthLocation_basic(): - greenwichel = EarthLocation.of_site('greenwich') + greenwichel = EarthLocation.of_site("greenwich") lon, lat, el = greenwichel.to_geodetic() - assert_quantity_allclose(lon, Longitude('0:0:0', unit=u.deg), - atol=10*u.arcsec) - assert_quantity_allclose(lat, Latitude('51:28:40', unit=u.deg), - atol=1*u.arcsec) - assert_quantity_allclose(el, 46*u.m, atol=1*u.m) + assert_quantity_allclose(lon, Longitude("0:0:0", unit=u.deg), atol=10 * u.arcsec) + assert_quantity_allclose(lat, Latitude("51:28:40", unit=u.deg), atol=1 * u.arcsec) + assert_quantity_allclose(el, 46 * u.m, atol=1 * u.m) names = EarthLocation.get_site_names() - assert 'greenwich' in names - assert 'example_site' in names + assert "greenwich" in names + assert "example_site" in names - with pytest.raises(KeyError) as exc: - EarthLocation.of_site('nonexistent site') - assert exc.value.args[0] == "Site 'nonexistent site' not in database. Use EarthLocation.get_site_names to see available sites." + with pytest.raises( + KeyError, + match="Site 'nonexistent' not in database. Use EarthLocation.get_site_names", + ): + EarthLocation.of_site("nonexistent") def test_EarthLocation_state_offline(): @@ -94,7 +98,7 @@ def test_EarthLocation_state_offline(): assert oldreg is not newreg -@pytest.mark.remote_data(source='astropy') +@pytest.mark.remote_data(source="astropy") def test_EarthLocation_state_online(): EarthLocation._site_registry = None EarthLocation._get_site_registry(force_download=True) @@ -112,16 +116,16 @@ def test_registry(): assert len(reg.names) == 0 - names = ['sitea', 'site A'] - loc = EarthLocation.from_geodetic(lat=1*u.deg, lon=2*u.deg, height=3*u.km) + names = ["sitea", "site A"] + loc = EarthLocation.from_geodetic(lat=1 * u.deg, lon=2 * u.deg, height=3 * u.km) reg.add_site(names, loc) assert len(reg.names) == 2 - loc1 = reg['SIteA'] + loc1 = reg["SIteA"] assert loc1 is loc - loc2 = reg['sIte a'] + loc2 = reg["sIte a"] assert loc2 is loc @@ -130,6 +134,7 @@ def test_non_EarthLocation(): A regression test for a typo bug pointed out at the bottom of https://github.com/astropy/astropy/pull/4042 """ + class EarthLocation2(EarthLocation): pass @@ -138,9 +143,9 @@ class EarthLocation2(EarthLocation): # registry is cached on a per-class basis EarthLocation2._get_site_registry(force_builtin=True) - el2 = EarthLocation2.of_site('greenwich') + el2 = EarthLocation2.of_site("greenwich") assert type(el2) is EarthLocation2 - assert el2.info.name == 'Royal Observatory Greenwich' + assert el2.info.name == "Royal Observatory Greenwich" def check_builtin_matches_remote(download_url=True): @@ -160,7 +165,9 @@ def check_builtin_matches_remote(download_url=True): for name in builtin_registry.names: in_dl[name] = name in dl_registry if in_dl[name]: - matches[name] = quantity_allclose(builtin_registry[name].geocentric, dl_registry[name].geocentric) + matches[name] = quantity_allclose( + builtin_registry[name].geocentric, dl_registry[name].geocentric + ) else: matches[name] = False @@ -169,17 +176,29 @@ def check_builtin_matches_remote(download_url=True): print("In builtin registry but not in download:") for name in in_dl: if not in_dl[name]: - print(' ', name) + print(" ", name) print("In both but not the same value:") for name in matches: if not matches[name] and in_dl[name]: - print(' ', name, 'builtin:', builtin_registry[name], 'download:', dl_registry[name]) - assert False, "Builtin and download registry aren't consistent - failures printed to stdout" + print( + " ", + name, + "builtin:", + builtin_registry[name], + "download:", + dl_registry[name], + ) + assert False, ( + "Builtin and download registry aren't consistent - failures printed to" + " stdout" + ) def test_meta_present(): reg = get_builtin_sites() - greenwich = reg['greenwich'] - assert greenwich.info.meta['source'] == ('Ordnance Survey via ' - 'http://gpsinformation.net/main/greenwich.htm and UNESCO') + greenwich = reg["greenwich"] + assert ( + greenwich.info.meta["source"] + == "Ordnance Survey via http://gpsinformation.net/main/greenwich.htm and UNESCO" + ) diff --git a/astropy/coordinates/tests/test_sky_coord.py b/astropy/coordinates/tests/test_sky_coord.py index e08e992fe2a..4dcfba0d4d3 100644 --- a/astropy/coordinates/tests/test_sky_coord.py +++ b/astropy/coordinates/tests/test_sky_coord.py @@ -51,12 +51,12 @@ DEC = 2.0 * u.deg C_ICRS = ICRS(RA, DEC) C_FK5 = C_ICRS.transform_to(FK5()) -J2001 = Time('J2001') +J2001 = Time("J2001") def allclose(a, b, rtol=0.0, atol=None): if atol is None: - atol = 1.e-8 * getattr(a, 'unit', 1.) + atol = 1.0e-8 * getattr(a, "unit", 1.0) return quantity_allclose(a, b, rtol, atol) @@ -81,7 +81,7 @@ def test_is_transformable_to_str_input(): """ # make example SkyCoord - c = SkyCoord(90*u.deg, -11*u.deg) + c = SkyCoord(90 * u.deg, -11 * u.deg) # iterate through some frames, checking consistency names = frame_transform_graph.get_names() @@ -91,11 +91,15 @@ def test_is_transformable_to_str_input(): def test_transform_to(): - for frame in (FK5(), FK5(equinox=Time('J1975.0')), - FK4(), FK4(equinox=Time('J1975.0')), - SkyCoord(RA, DEC, frame='fk4', equinox='J1980')): + for frame in ( + FK5(), + FK5(equinox=Time("J1975.0")), + FK4(), + FK4(equinox=Time("J1975.0")), + SkyCoord(RA, DEC, frame="fk4", equinox="J1980"), + ): c_frame = C_ICRS.transform_to(frame) - s_icrs = SkyCoord(RA, DEC, frame='icrs') + s_icrs = SkyCoord(RA, DEC, frame="icrs") s_frame = s_icrs.transform_to(frame) assert allclose(c_frame.ra, s_frame.ra) assert allclose(c_frame.dec, s_frame.dec) @@ -107,14 +111,21 @@ def test_transform_to(): rt_frames = [ICRS, FK4, FK5, Galactic] for rt_frame0 in rt_frames: for rt_frame1 in rt_frames: - for equinox0 in (None, 'J1975.0'): - for obstime0 in (None, 'J1980.0'): - for equinox1 in (None, 'J1975.0'): - for obstime1 in (None, 'J1980.0'): - rt_sets.append((rt_frame0, rt_frame1, - equinox0, equinox1, - obstime0, obstime1)) -rt_args = ('frame0', 'frame1', 'equinox0', 'equinox1', 'obstime0', 'obstime1') + for equinox0 in (None, "J1975.0"): + for obstime0 in (None, "J1980.0"): + for equinox1 in (None, "J1975.0"): + for obstime1 in (None, "J1980.0"): + rt_sets.append( + ( + rt_frame0, + rt_frame1, + equinox0, + equinox1, + obstime0, + obstime1, + ) + ) +rt_args = ("frame0", "frame1", "equinox0", "equinox1", "obstime0", "obstime1") @pytest.mark.parametrize(rt_args, rt_sets) @@ -122,8 +133,8 @@ def test_round_tripping(frame0, frame1, equinox0, equinox1, obstime0, obstime1): """ Test round tripping out and back using transform_to in every combination. """ - attrs0 = {'equinox': equinox0, 'obstime': obstime0} - attrs1 = {'equinox': equinox1, 'obstime': obstime1} + attrs0 = {"equinox": equinox0, "obstime": obstime0} + attrs1 = {"equinox": equinox1, "obstime": obstime1} # Remove None values attrs0 = {k: v for k, v in attrs0.items() if v is not None} @@ -133,19 +144,21 @@ def test_round_tripping(frame0, frame1, equinox0, equinox1, obstime0, obstime1): sc = SkyCoord(RA, DEC, frame=frame0, **attrs0) # Keep only frame attributes for frame1 - attrs1 = {attr: val for attr, val in attrs1.items() - if attr in frame1.frame_attributes} + attrs1 = { + attr: val for attr, val in attrs1.items() if attr in frame1.frame_attributes + } sc2 = sc.transform_to(frame1(**attrs1)) # When coming back only keep frame0 attributes for transform_to - attrs0 = {attr: val for attr, val in attrs0.items() - if attr in frame0.frame_attributes} + attrs0 = { + attr: val for attr, val in attrs0.items() if attr in frame0.frame_attributes + } # also, if any are None, fill in with defaults for attrnm in frame0.frame_attributes: if attrs0.get(attrnm, None) is None: - if attrnm == 'obstime' and frame0.get_frame_attr_defaults()[attrnm] is None: - if 'equinox' in attrs0: - attrs0[attrnm] = attrs0['equinox'] + if attrnm == "obstime" and frame0.get_frame_attr_defaults()[attrnm] is None: + if "equinox" in attrs0: + attrs0[attrnm] = attrs0["equinox"] else: attrs0[attrnm] = frame0.get_frame_attr_defaults()[attrnm] sc_rt = sc2.transform_to(frame0(**attrs0)) @@ -166,72 +179,72 @@ def test_coord_init_string(): """ Spherical or Cartesian representation input coordinates. """ - sc = SkyCoord('1d 2d') + sc = SkyCoord("1d 2d") assert allclose(sc.ra, 1 * u.deg) assert allclose(sc.dec, 2 * u.deg) - sc = SkyCoord('1d', '2d') + sc = SkyCoord("1d", "2d") assert allclose(sc.ra, 1 * u.deg) assert allclose(sc.dec, 2 * u.deg) - sc = SkyCoord('1°2′3″', '2°3′4″') - assert allclose(sc.ra, Angle('1°2′3″')) - assert allclose(sc.dec, Angle('2°3′4″')) + sc = SkyCoord("1°2′3″", "2°3′4″") + assert allclose(sc.ra, Angle("1°2′3″")) + assert allclose(sc.dec, Angle("2°3′4″")) - sc = SkyCoord('1°2′3″ 2°3′4″') - assert allclose(sc.ra, Angle('1°2′3″')) - assert allclose(sc.dec, Angle('2°3′4″')) + sc = SkyCoord("1°2′3″ 2°3′4″") + assert allclose(sc.ra, Angle("1°2′3″")) + assert allclose(sc.dec, Angle("2°3′4″")) with pytest.raises(ValueError) as err: - SkyCoord('1d 2d 3d') + SkyCoord("1d 2d 3d") assert "Cannot parse first argument data" in str(err.value) - sc1 = SkyCoord('8 00 00 +5 00 00.0', unit=(u.hour, u.deg), frame='icrs') + sc1 = SkyCoord("8 00 00 +5 00 00.0", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc1, SkyCoord) assert allclose(sc1.ra, Angle(120 * u.deg)) assert allclose(sc1.dec, Angle(5 * u.deg)) - sc11 = SkyCoord('8h00m00s+5d00m00.0s', unit=(u.hour, u.deg), frame='icrs') + sc11 = SkyCoord("8h00m00s+5d00m00.0s", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc11, SkyCoord) assert allclose(sc1.ra, Angle(120 * u.deg)) assert allclose(sc1.dec, Angle(5 * u.deg)) - sc2 = SkyCoord('8 00 -5 00 00.0', unit=(u.hour, u.deg), frame='icrs') + sc2 = SkyCoord("8 00 -5 00 00.0", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc2, SkyCoord) assert allclose(sc2.ra, Angle(120 * u.deg)) assert allclose(sc2.dec, Angle(-5 * u.deg)) - sc3 = SkyCoord('8 00 -5 00.6', unit=(u.hour, u.deg), frame='icrs') + sc3 = SkyCoord("8 00 -5 00.6", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc3, SkyCoord) assert allclose(sc3.ra, Angle(120 * u.deg)) assert allclose(sc3.dec, Angle(-5.01 * u.deg)) - sc4 = SkyCoord('J080000.00-050036.00', unit=(u.hour, u.deg), frame='icrs') + sc4 = SkyCoord("J080000.00-050036.00", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc4, SkyCoord) assert allclose(sc4.ra, Angle(120 * u.deg)) assert allclose(sc4.dec, Angle(-5.01 * u.deg)) - sc41 = SkyCoord('J080000+050036', unit=(u.hour, u.deg), frame='icrs') + sc41 = SkyCoord("J080000+050036", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc41, SkyCoord) assert allclose(sc41.ra, Angle(120 * u.deg)) assert allclose(sc41.dec, Angle(+5.01 * u.deg)) - sc5 = SkyCoord('8h00.6m -5d00.6m', unit=(u.hour, u.deg), frame='icrs') + sc5 = SkyCoord("8h00.6m -5d00.6m", unit=(u.hour, u.deg), frame="icrs") assert isinstance(sc5, SkyCoord) assert allclose(sc5.ra, Angle(120.15 * u.deg)) assert allclose(sc5.dec, Angle(-5.01 * u.deg)) - sc6 = SkyCoord('8h00.6m -5d00.6m', unit=(u.hour, u.deg), frame='fk4') + sc6 = SkyCoord("8h00.6m -5d00.6m", unit=(u.hour, u.deg), frame="fk4") assert isinstance(sc6, SkyCoord) assert allclose(sc6.ra, Angle(120.15 * u.deg)) assert allclose(sc6.dec, Angle(-5.01 * u.deg)) - sc61 = SkyCoord('8h00.6m-5d00.6m', unit=(u.hour, u.deg), frame='fk4') + sc61 = SkyCoord("8h00.6m-5d00.6m", unit=(u.hour, u.deg), frame="fk4") assert isinstance(sc61, SkyCoord) assert allclose(sc6.ra, Angle(120.15 * u.deg)) assert allclose(sc6.dec, Angle(-5.01 * u.deg)) - sc61 = SkyCoord('8h00.6-5d00.6', unit=(u.hour, u.deg), frame='fk4') + sc61 = SkyCoord("8h00.6-5d00.6", unit=(u.hour, u.deg), frame="fk4") assert isinstance(sc61, SkyCoord) assert allclose(sc6.ra, Angle(120.15 * u.deg)) assert allclose(sc6.dec, Angle(-5.01 * u.deg)) @@ -242,36 +255,47 @@ def test_coord_init_string(): assert allclose(sc7.dec, Angle(12.406 * u.deg)) with pytest.raises(ValueError): - SkyCoord('8 00 -5 00.6', unit=(u.deg, u.deg), frame='galactic') + SkyCoord("8 00 -5 00.6", unit=(u.deg, u.deg), frame="galactic") def test_coord_init_unit(): """ Test variations of the unit keyword. """ - for unit in ('deg', 'deg,deg', ' deg , deg ', u.deg, (u.deg, u.deg), - np.array(['deg', 'deg'])): + for unit in ( + "deg", + "deg,deg", + " deg , deg ", + u.deg, + (u.deg, u.deg), + np.array(["deg", "deg"]), + ): sc = SkyCoord(1, 2, unit=unit) assert allclose(sc.ra, Angle(1 * u.deg)) assert allclose(sc.dec, Angle(2 * u.deg)) - for unit in ('hourangle', 'hourangle,hourangle', ' hourangle , hourangle ', - u.hourangle, [u.hourangle, u.hourangle]): + for unit in ( + "hourangle", + "hourangle,hourangle", + " hourangle , hourangle ", + u.hourangle, + [u.hourangle, u.hourangle], + ): sc = SkyCoord(1, 2, unit=unit) assert allclose(sc.ra, Angle(15 * u.deg)) assert allclose(sc.dec, Angle(30 * u.deg)) - for unit in ('hourangle,deg', (u.hourangle, u.deg)): + for unit in ("hourangle,deg", (u.hourangle, u.deg)): sc = SkyCoord(1, 2, unit=unit) assert allclose(sc.ra, Angle(15 * u.deg)) assert allclose(sc.dec, Angle(2 * u.deg)) - for unit in ('deg,deg,deg,deg', [u.deg, u.deg, u.deg, u.deg], None): + for unit in ("deg,deg,deg,deg", [u.deg, u.deg, u.deg, u.deg], None): with pytest.raises(ValueError) as err: SkyCoord(1, 2, unit=unit) - assert 'Unit keyword must have one to three unit values' in str(err.value) + assert "Unit keyword must have one to three unit values" in str(err.value) - for unit in ('m', (u.m, u.deg), ''): + for unit in ("m", (u.m, u.deg), ""): with pytest.raises(u.UnitsError) as err: SkyCoord(1, 2, unit=unit) @@ -280,43 +304,42 @@ def test_coord_init_list(): """ Spherical or Cartesian representation input coordinates. """ - sc = SkyCoord([('1d', '2d'), - (1 * u.deg, 2 * u.deg), - '1d 2d', - ('1°', '2°'), - '1° 2°'], unit='deg') - assert allclose(sc.ra, Angle('1d')) - assert allclose(sc.dec, Angle('2d')) + sc = SkyCoord( + [("1d", "2d"), (1 * u.deg, 2 * u.deg), "1d 2d", ("1°", "2°"), "1° 2°"], + unit="deg", + ) + assert allclose(sc.ra, Angle("1d")) + assert allclose(sc.dec, Angle("2d")) with pytest.raises(ValueError) as err: - SkyCoord(['1d 2d 3d']) + SkyCoord(["1d 2d 3d"]) assert "Cannot parse first argument data" in str(err.value) with pytest.raises(ValueError) as err: - SkyCoord([('1d', '2d', '3d')]) + SkyCoord([("1d", "2d", "3d")]) assert "Cannot parse first argument data" in str(err.value) sc = SkyCoord([1 * u.deg, 1 * u.deg], [2 * u.deg, 2 * u.deg]) - assert allclose(sc.ra, Angle('1d')) - assert allclose(sc.dec, Angle('2d')) + assert allclose(sc.ra, Angle("1d")) + assert allclose(sc.dec, Angle("2d")) - with pytest.raises(ValueError) as err: + with pytest.raises( + ValueError, + match="One or more elements of input sequence does not have a length", + ): SkyCoord([1 * u.deg, 2 * u.deg]) # this list is taken as RA w/ missing dec - assert "One or more elements of input sequence does not have a length" in str(err.value) def test_coord_init_array(): """ Input in the form of a list array or numpy array """ - for a in (['1 2', '3 4'], - [['1', '2'], ['3', '4']], - [[1, 2], [3, 4]]): - sc = SkyCoord(a, unit='deg') + for a in (["1 2", "3 4"], [["1", "2"], ["3", "4"]], [[1, 2], [3, 4]]): + sc = SkyCoord(a, unit="deg") assert allclose(sc.ra - [1, 3] * u.deg, 0 * u.deg) assert allclose(sc.dec - [2, 4] * u.deg, 0 * u.deg) - sc = SkyCoord(np.array(a), unit='deg') + sc = SkyCoord(np.array(a), unit="deg") assert allclose(sc.ra - [1, 3] * u.deg, 0 * u.deg) assert allclose(sc.dec - [2, 4] * u.deg, 0 * u.deg) @@ -326,17 +349,17 @@ def test_coord_init_representation(): Spherical or Cartesian representation input coordinates. """ coord = SphericalRepresentation(lon=8 * u.deg, lat=5 * u.deg, distance=1 * u.kpc) - sc = SkyCoord(coord, frame='icrs') + sc = SkyCoord(coord, frame="icrs") assert allclose(sc.ra, coord.lon) assert allclose(sc.dec, coord.lat) assert allclose(sc.distance, coord.distance) with pytest.raises(ValueError) as err: - SkyCoord(coord, frame='icrs', ra='1d') + SkyCoord(coord, frame="icrs", ra="1d") assert "conflicts with keyword argument 'ra'" in str(err.value) coord = CartesianRepresentation(1 * u.one, 2 * u.one, 3 * u.one) - sc = SkyCoord(coord, frame='icrs') + sc = SkyCoord(coord, frame="icrs") sc_cart = sc.represent_as(CartesianRepresentation) assert allclose(sc_cart.x, 1.0) assert allclose(sc_cart.y, 2.0) @@ -348,30 +371,30 @@ def test_frame_init(): Different ways of providing the frame. """ - sc = SkyCoord(RA, DEC, frame='icrs') - assert sc.frame.name == 'icrs' + sc = SkyCoord(RA, DEC, frame="icrs") + assert sc.frame.name == "icrs" sc = SkyCoord(RA, DEC, frame=ICRS) - assert sc.frame.name == 'icrs' + assert sc.frame.name == "icrs" sc = SkyCoord(sc) - assert sc.frame.name == 'icrs' + assert sc.frame.name == "icrs" sc = SkyCoord(C_ICRS) - assert sc.frame.name == 'icrs' + assert sc.frame.name == "icrs" - SkyCoord(C_ICRS, frame='icrs') - assert sc.frame.name == 'icrs' + SkyCoord(C_ICRS, frame="icrs") + assert sc.frame.name == "icrs" with pytest.raises(ValueError) as err: - SkyCoord(C_ICRS, frame='galactic') - assert 'Cannot override frame=' in str(err.value) + SkyCoord(C_ICRS, frame="galactic") + assert "Cannot override frame=" in str(err.value) def test_equal(): - obstime = 'B1955' - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, obstime=obstime) - sc2 = SkyCoord([1, 20]*u.deg, [3, 4]*u.deg, obstime=obstime) + obstime = "B1955" + sc1 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, obstime=obstime) + sc2 = SkyCoord([1, 20] * u.deg, [3, 4] * u.deg, obstime=obstime) # Compare arrays and scalars eq = sc1 == sc2 @@ -388,8 +411,8 @@ def test_equal(): assert np.all(ne == [False, True]) # With diff only in velocity - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 2]*u.km/u.s) - sc2 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 20]*u.km/u.s) + sc1 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, radial_velocity=[1, 2] * u.km / u.s) + sc2 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, radial_velocity=[1, 20] * u.km / u.s) eq = sc1 == sc2 ne = sc1 != sc2 @@ -400,18 +423,22 @@ def test_equal(): def test_equal_different_type(): - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, obstime='B1955') + sc1 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, obstime="B1955") # Test equals and not equals operators against different types - assert sc1 != 'a string' - assert not (sc1 == 'a string') + assert sc1 != "a string" + assert not (sc1 == "a string") def test_equal_exceptions(): - sc1 = SkyCoord(1*u.deg, 2*u.deg, obstime='B1955') - sc2 = SkyCoord(1*u.deg, 2*u.deg) - with pytest.raises(ValueError, match=r"cannot compare: extra frame " - r"attribute 'obstime' is not equivalent \(perhaps compare the " - r"frames directly to avoid this exception\)"): + sc1 = SkyCoord(1 * u.deg, 2 * u.deg, obstime="B1955") + sc2 = SkyCoord(1 * u.deg, 2 * u.deg) + with pytest.raises( + ValueError, + match=( + "cannot compare: extra frame attribute 'obstime' is not equivalent" + r" \(perhaps compare the frames directly to avoid this exception\)" + ), + ): sc1 == sc2 # Note that this exception is the only one raised directly in SkyCoord. # All others come from lower-level classes and are tested in test_frames.py. @@ -423,7 +450,7 @@ def test_attr_inheritance(): equinox should be inherited to the SkyCoord. If there is a conflict then raise an exception. """ - sc = SkyCoord(1, 2, frame='icrs', unit='deg', equinox='J1999', obstime='J2001') + sc = SkyCoord(1, 2, frame="icrs", unit="deg", equinox="J1999", obstime="J2001") sc2 = SkyCoord(sc) assert sc2.equinox == sc.equinox assert sc2.obstime == sc.obstime @@ -438,7 +465,7 @@ def test_attr_inheritance(): assert allclose(sc2.dec, sc.dec) assert allclose(sc2.distance, sc.distance) - sc = SkyCoord(1, 2, frame='fk4', unit='deg', equinox='J1999', obstime='J2001') + sc = SkyCoord(1, 2, frame="fk4", unit="deg", equinox="J1999", obstime="J2001") sc2 = SkyCoord(sc) assert sc2.equinox == sc.equinox assert sc2.obstime == sc.obstime @@ -454,20 +481,20 @@ def test_attr_inheritance(): assert allclose(sc2.distance, sc.distance) -@pytest.mark.parametrize('frame', ['fk4', 'fk5', 'icrs']) +@pytest.mark.parametrize("frame", ["fk4", "fk5", "icrs"]) def test_setitem_no_velocity(frame): """Test different flavors of item setting for a SkyCoord without a velocity for different frames. Include a frame attribute that is sometimes an actual frame attribute and sometimes an extra frame attribute. """ - sc0 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, obstime='B1955', frame=frame) - sc2 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg, obstime='B1955', frame=frame) + sc0 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, obstime="B1955", frame=frame) + sc2 = SkyCoord([10, 20] * u.deg, [30, 40] * u.deg, obstime="B1955", frame=frame) sc1 = sc0.copy() sc1[1] = sc2[0] assert np.allclose(sc1.ra.to_value(u.deg), [1, 10]) assert np.allclose(sc1.dec.to_value(u.deg), [3, 30]) - assert sc1.obstime == Time('B1955') + assert sc1.obstime == Time("B1955") assert sc1.frame.name == frame sc1 = sc0.copy() @@ -487,29 +514,38 @@ def test_setitem_no_velocity(frame): def test_setitem_initially_broadcast(): - sc = SkyCoord(np.ones((2, 1))*u.deg, np.ones((1, 3))*u.deg) - sc[1, 1] = SkyCoord(0*u.deg, 0*u.deg) - expected = np.ones((2, 3))*u.deg - expected[1, 1] = 0. + sc = SkyCoord(np.ones((2, 1)) * u.deg, np.ones((1, 3)) * u.deg) + sc[1, 1] = SkyCoord(0 * u.deg, 0 * u.deg) + expected = np.ones((2, 3)) * u.deg + expected[1, 1] = 0.0 assert np.all(sc.ra == expected) assert np.all(sc.dec == expected) def test_setitem_velocities(): - """Test different flavors of item setting for a SkyCoord with a velocity. - """ - sc0 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, radial_velocity=[1, 2]*u.km/u.s, - obstime='B1950', frame='fk4') - sc2 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg, radial_velocity=[10, 20]*u.km/u.s, - obstime='B1950', frame='fk4') + """Test different flavors of item setting for a SkyCoord with a velocity.""" + sc0 = SkyCoord( + [1, 2] * u.deg, + [3, 4] * u.deg, + radial_velocity=[1, 2] * u.km / u.s, + obstime="B1950", + frame="fk4", + ) + sc2 = SkyCoord( + [10, 20] * u.deg, + [30, 40] * u.deg, + radial_velocity=[10, 20] * u.km / u.s, + obstime="B1950", + frame="fk4", + ) sc1 = sc0.copy() sc1[1] = sc2[0] assert np.allclose(sc1.ra.to_value(u.deg), [1, 10]) assert np.allclose(sc1.dec.to_value(u.deg), [3, 30]) assert np.allclose(sc1.radial_velocity.to_value(u.km / u.s), [1, 10]) - assert sc1.obstime == Time('B1950') - assert sc1.frame.name == 'fk4' + assert sc1.obstime == Time("B1950") + assert sc1.frame.name == "fk4" sc1 = sc0.copy() sc1[:] = sc2[0] @@ -534,83 +570,104 @@ def test_setitem_exceptions(): class SkyCoordSub(SkyCoord): pass - obstime = 'B1955' - sc0 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, frame='fk4') - sc2 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg, frame='fk4', obstime=obstime) + obstime = "B1955" + sc0 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg, frame="fk4") + sc2 = SkyCoord([10, 20] * u.deg, [30, 40] * u.deg, frame="fk4", obstime=obstime) sc1 = SkyCoordSub(sc0) - with pytest.raises(TypeError, match='an only set from object of same class: ' - 'SkyCoordSub vs. SkyCoord'): + with pytest.raises( + TypeError, + match="an only set from object of same class: SkyCoordSub vs. SkyCoord", + ): sc1[0] = sc2[0] - sc1 = SkyCoord(sc0.ra, sc0.dec, frame='fk4', obstime='B2001') - with pytest.raises(ValueError, match='can only set frame item from an equivalent frame'): + sc1 = SkyCoord(sc0.ra, sc0.dec, frame="fk4", obstime="B2001") + with pytest.raises( + ValueError, match="can only set frame item from an equivalent frame" + ): sc1.frame[0] = sc2.frame[0] - sc1 = SkyCoord(sc0.ra[0], sc0.dec[0], frame='fk4', obstime=obstime) - with pytest.raises(TypeError, match="scalar 'FK4' frame object does not support " - 'item assignment'): + sc1 = SkyCoord(sc0.ra[0], sc0.dec[0], frame="fk4", obstime=obstime) + with pytest.raises( + TypeError, match="scalar 'FK4' frame object does not support item assignment" + ): sc1[0] = sc2[0] # Different differentials - sc1 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg, - pm_ra_cosdec=[1, 2]*u.mas/u.yr, pm_dec=[3, 4]*u.mas/u.yr) - sc2 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg, radial_velocity=[10, 20]*u.km/u.s) - with pytest.raises(TypeError, match='can only set from object of same class: ' - 'UnitSphericalCosLatDifferential vs. RadialDifferential'): + sc1 = SkyCoord( + [1, 2] * u.deg, + [3, 4] * u.deg, + pm_ra_cosdec=[1, 2] * u.mas / u.yr, + pm_dec=[3, 4] * u.mas / u.yr, + ) + sc2 = SkyCoord( + [10, 20] * u.deg, [30, 40] * u.deg, radial_velocity=[10, 20] * u.km / u.s + ) + with pytest.raises( + TypeError, + match=( + "can only set from object of same class: " + "UnitSphericalCosLatDifferential vs. RadialDifferential" + ), + ): sc1[0] = sc2[0] def test_insert(): - sc0 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg) - sc1 = SkyCoord(5*u.deg, 6*u.deg) - sc3 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg) - sc4 = SkyCoord([[1, 2], [3, 4]]*u.deg, - [[5, 6], [7, 8]]*u.deg) - sc5 = SkyCoord([[10, 2], [30, 4]]*u.deg, - [[50, 6], [70, 8]]*u.deg) + sc0 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg) + sc1 = SkyCoord(5 * u.deg, 6 * u.deg) + sc3 = SkyCoord([10, 20] * u.deg, [30, 40] * u.deg) + sc4 = SkyCoord([[1, 2], [3, 4]] * u.deg, [[5, 6], [7, 8]] * u.deg) + sc5 = SkyCoord([[10, 2], [30, 4]] * u.deg, [[50, 6], [70, 8]] * u.deg) # Insert a scalar sc = sc0.insert(1, sc1) - assert skycoord_equal(sc, SkyCoord([1, 5, 2]*u.deg, [3, 6, 4]*u.deg)) + assert skycoord_equal(sc, SkyCoord([1, 5, 2] * u.deg, [3, 6, 4] * u.deg)) # Insert length=2 array at start of array sc = sc0.insert(0, sc3) - assert skycoord_equal(sc, SkyCoord([10, 20, 1, 2]*u.deg, [30, 40, 3, 4]*u.deg)) + assert skycoord_equal(sc, SkyCoord([10, 20, 1, 2] * u.deg, [30, 40, 3, 4] * u.deg)) # Insert length=2 array at end of array sc = sc0.insert(2, sc3) - assert skycoord_equal(sc, SkyCoord([1, 2, 10, 20]*u.deg, [3, 4, 30, 40]*u.deg)) + assert skycoord_equal(sc, SkyCoord([1, 2, 10, 20] * u.deg, [3, 4, 30, 40] * u.deg)) # Multidimensional sc = sc4.insert(1, sc5) - assert skycoord_equal(sc, SkyCoord([[1, 2], [10, 2], [30, 4], [3, 4]]*u.deg, - [[5, 6], [50, 6], [70, 8], [7, 8]]*u.deg)) + assert skycoord_equal( + sc, + SkyCoord( + [[1, 2], [10, 2], [30, 4], [3, 4]] * u.deg, + [[5, 6], [50, 6], [70, 8], [7, 8]] * u.deg, + ), + ) def test_insert_exceptions(): - sc0 = SkyCoord([1, 2]*u.deg, [3, 4]*u.deg) - sc1 = SkyCoord(5*u.deg, 6*u.deg) + sc0 = SkyCoord([1, 2] * u.deg, [3, 4] * u.deg) + sc1 = SkyCoord(5 * u.deg, 6 * u.deg) # sc3 = SkyCoord([10, 20]*u.deg, [30, 40]*u.deg) - sc4 = SkyCoord([[1, 2], [3, 4]]*u.deg, - [[5, 6], [7, 8]]*u.deg) + sc4 = SkyCoord([[1, 2], [3, 4]] * u.deg, [[5, 6], [7, 8]] * u.deg) - with pytest.raises(TypeError, match='cannot insert into scalar'): + with pytest.raises(TypeError, match="cannot insert into scalar"): sc1.insert(0, sc0) - with pytest.raises(ValueError, match='axis must be 0'): + with pytest.raises(ValueError, match="axis must be 0"): sc0.insert(0, sc1, axis=1) - with pytest.raises(TypeError, match='obj arg must be an integer'): + with pytest.raises(TypeError, match="obj arg must be an integer"): sc0.insert(slice(None), sc0) - with pytest.raises(IndexError, match='index -100 is out of bounds for axis 0 ' - 'with size 2'): + with pytest.raises( + IndexError, match="index -100 is out of bounds for axis 0 with size 2" + ): sc0.insert(-100, sc0) # Bad shape - with pytest.raises(ValueError, match='could not broadcast input array from ' - r'shape \(2,2\) into shape \(2,?\)'): + with pytest.raises( + ValueError, + match=r"could not broadcast input array from shape \(2,2\) into shape \(2,?\)", + ): sc0.insert(0, sc4) @@ -618,33 +675,33 @@ def test_attr_conflicts(): """ Check conflicts resolution between coordinate attributes and init kwargs. """ - sc = SkyCoord(1, 2, frame='icrs', unit='deg', equinox='J1999', obstime='J2001') + sc = SkyCoord(1, 2, frame="icrs", unit="deg", equinox="J1999", obstime="J2001") # OK if attrs both specified but with identical values - SkyCoord(sc, equinox='J1999', obstime='J2001') + SkyCoord(sc, equinox="J1999", obstime="J2001") # OK because sc.frame doesn't have obstime - SkyCoord(sc.frame, equinox='J1999', obstime='J2100') + SkyCoord(sc.frame, equinox="J1999", obstime="J2100") # Not OK if attrs don't match with pytest.raises(ValueError) as err: - SkyCoord(sc, equinox='J1999', obstime='J2002') + SkyCoord(sc, equinox="J1999", obstime="J2002") assert "Coordinate attribute 'obstime'=" in str(err.value) # Same game but with fk4 which has equinox and obstime frame attrs - sc = SkyCoord(1, 2, frame='fk4', unit='deg', equinox='J1999', obstime='J2001') + sc = SkyCoord(1, 2, frame="fk4", unit="deg", equinox="J1999", obstime="J2001") # OK if attrs both specified but with identical values - SkyCoord(sc, equinox='J1999', obstime='J2001') + SkyCoord(sc, equinox="J1999", obstime="J2001") # Not OK if SkyCoord attrs don't match with pytest.raises(ValueError) as err: - SkyCoord(sc, equinox='J1999', obstime='J2002') + SkyCoord(sc, equinox="J1999", obstime="J2002") assert "Frame attribute 'obstime' has conflicting" in str(err.value) # Not OK because sc.frame has different attrs with pytest.raises(ValueError) as err: - SkyCoord(sc.frame, equinox='J1999', obstime='J2002') + SkyCoord(sc.frame, equinox="J1999", obstime="J2002") assert "Frame attribute 'obstime' has conflicting" in str(err.value) @@ -654,17 +711,17 @@ def test_frame_attr_getattr(): from self.frame when that object has the relevant attribute, otherwise from self. """ - sc = SkyCoord(1, 2, frame='icrs', unit='deg', equinox='J1999', obstime='J2001') - assert sc.equinox == 'J1999' # Just the raw value (not validated) - assert sc.obstime == 'J2001' + sc = SkyCoord(1, 2, frame="icrs", unit="deg", equinox="J1999", obstime="J2001") + assert sc.equinox == "J1999" # Just the raw value (not validated) + assert sc.obstime == "J2001" - sc = SkyCoord(1, 2, frame='fk4', unit='deg', equinox='J1999', obstime='J2001') - assert sc.equinox == Time('J1999') # Coming from the self.frame object - assert sc.obstime == Time('J2001') + sc = SkyCoord(1, 2, frame="fk4", unit="deg", equinox="J1999", obstime="J2001") + assert sc.equinox == Time("J1999") # Coming from the self.frame object + assert sc.obstime == Time("J2001") - sc = SkyCoord(1, 2, frame='fk4', unit='deg', equinox='J1999') - assert sc.equinox == Time('J1999') - assert sc.obstime == Time('J1999') + sc = SkyCoord(1, 2, frame="fk4", unit="deg", equinox="J1999") + assert sc.equinox == Time("J1999") + assert sc.obstime == Time("J1999") def test_to_string(): @@ -673,24 +730,24 @@ def test_to_string(): for a single input coordinate and and 1-element list. It does not test the underlying `Angle.to_string` method itself. """ - coord = '1h2m3s 1d2m3s' + coord = "1h2m3s 1d2m3s" for wrap in (lambda x: x, lambda x: [x]): sc = SkyCoord(wrap(coord)) - assert sc.to_string() == wrap('15.5125 1.03417') - assert sc.to_string('dms') == wrap('15d30m45s 1d02m03s') - assert sc.to_string('hmsdms') == wrap('01h02m03s +01d02m03s') - with_kwargs = sc.to_string('hmsdms', precision=3, pad=True, alwayssign=True) - assert with_kwargs == wrap('+01h02m03.000s +01d02m03.000s') + assert sc.to_string() == wrap("15.5125 1.03417") + assert sc.to_string("dms") == wrap("15d30m45s 1d02m03s") + assert sc.to_string("hmsdms") == wrap("01h02m03s +01d02m03s") + with_kwargs = sc.to_string("hmsdms", precision=3, pad=True, alwayssign=True) + assert with_kwargs == wrap("+01h02m03.000s +01d02m03.000s") -@pytest.mark.parametrize('cls_other', [SkyCoord, ICRS]) +@pytest.mark.parametrize("cls_other", [SkyCoord, ICRS]) def test_seps(cls_other): sc1 = SkyCoord(0 * u.deg, 1 * u.deg) sc2 = cls_other(0 * u.deg, 2 * u.deg) sep = sc1.separation(sc2) - assert (sep - 1 * u.deg)/u.deg < 1e-10 + assert (sep - 1 * u.deg) / u.deg < 1e-10 with pytest.raises(ValueError): sc1.separation_3d(sc2) @@ -703,42 +760,42 @@ def test_seps(cls_other): def test_repr(): - sc1 = SkyCoord(0 * u.deg, 1 * u.deg, frame='icrs') - sc2 = SkyCoord(1 * u.deg, 1 * u.deg, frame='icrs', distance=1 * u.kpc) + sc1 = SkyCoord(0 * u.deg, 1 * u.deg, frame="icrs") + sc2 = SkyCoord(1 * u.deg, 1 * u.deg, frame="icrs", distance=1 * u.kpc) - assert repr(sc1) == ('') - assert repr(sc2) == ('') + assert repr(sc1) == "" + assert ( + repr(sc2) + == "" + ) - sc3 = SkyCoord(0.25 * u.deg, [1, 2.5] * u.deg, frame='icrs') - assert repr(sc3).startswith('') + assert repr(sc_default) == "" def test_repr_altaz(): - sc2 = SkyCoord(1 * u.deg, 1 * u.deg, frame='icrs', distance=1 * u.kpc) + sc2 = SkyCoord(1 * u.deg, 1 * u.deg, frame="icrs", distance=1 * u.kpc) loc = EarthLocation(-2309223 * u.m, -3695529 * u.m, -4641767 * u.m) - time = Time('2005-03-21 00:00:00') + time = Time("2005-03-21 00:00:00") sc4 = sc2.transform_to(AltAz(location=loc, obstime=time)) - assert repr(sc4).startswith(" 270*u.degree) + assert np.all(res < 360 * u.degree) + assert np.all(res > 270 * u.degree) - cicrs = SkyCoord(0*u.deg, 0*u.deg, frame='icrs') - cfk5 = SkyCoord(1*u.deg, 0*u.deg, frame='fk5') + cicrs = SkyCoord(0 * u.deg, 0 * u.deg, frame="icrs") + cfk5 = SkyCoord(1 * u.deg, 0 * u.deg, frame="fk5") # because of the frame transform, it's just a *bit* more than 90 degrees assert cicrs.position_angle(cfk5) > 90.0 * u.deg assert cicrs.position_angle(cfk5) < 91.0 * u.deg @@ -826,9 +883,10 @@ def test_position_angle(): def test_position_angle_directly(): """Regression check for #3800: position_angle should accept floats.""" from astropy.coordinates.angle_utilities import position_angle - result = position_angle(10., 20., 10., 20.) + + result = position_angle(10.0, 20.0, 10.0, 20.0) assert result.unit is u.radian - assert result.value == 0. + assert result.value == 0.0 def test_sep_pa_equivalence(): @@ -837,8 +895,8 @@ def test_sep_pa_equivalence(): PA and separation from object 1 to 2 should be consistent with those from 2 to 1 """ - cfk5 = SkyCoord(1*u.deg, 0*u.deg, frame='fk5') - cfk5B1950 = SkyCoord(1*u.deg, 0*u.deg, frame='fk5', equinox='B1950') + cfk5 = SkyCoord(1 * u.deg, 0 * u.deg, frame="fk5") + cfk5B1950 = SkyCoord(1 * u.deg, 0 * u.deg, frame="fk5", equinox="B1950") # test with both default and explicit equinox #5722 and #3106 sep_forward = cfk5.separation(cfk5B1950) sep_backward = cfk5B1950.separation(cfk5) @@ -847,10 +905,11 @@ def test_sep_pa_equivalence(): posang_forward = cfk5.position_angle(cfk5B1950) posang_backward = cfk5B1950.position_angle(cfk5) assert posang_forward != 0 and posang_backward != 0 - assert 179 < (posang_forward - posang_backward).wrap_at(360*u.deg).degree < 181 - dcfk5 = SkyCoord(1*u.deg, 0*u.deg, frame='fk5', distance=1*u.pc) - dcfk5B1950 = SkyCoord(1*u.deg, 0*u.deg, frame='fk5', equinox='B1950', - distance=1.*u.pc) + assert 179 < (posang_forward - posang_backward).wrap_at(360 * u.deg).degree < 181 + dcfk5 = SkyCoord(1 * u.deg, 0 * u.deg, frame="fk5", distance=1 * u.pc) + dcfk5B1950 = SkyCoord( + 1 * u.deg, 0 * u.deg, frame="fk5", equinox="B1950", distance=1.0 * u.pc + ) sep3d_forward = dcfk5.separation_3d(dcfk5B1950) sep3d_backward = dcfk5B1950.separation_3d(dcfk5) assert sep3d_forward != 0 and sep3d_backward != 0 @@ -860,19 +919,39 @@ def test_sep_pa_equivalence(): def test_directional_offset_by(): # Round-trip tests: where is sc2 from sc1? # Use those offsets from sc1 and verify you get to sc2. - npoints = 7 # How many points when doing vectors of SkyCoords - for sc1 in [SkyCoord(0*u.deg,-90*u.deg), # South pole - SkyCoord(0 * u.deg, 90 * u.deg), # North pole - SkyCoord(1*u.deg,2*u.deg), - SkyCoord(np.linspace(0,359,npoints),np.linspace(-90, 90,npoints), - unit=u.deg, frame='fk4'), - SkyCoord(np.linspace(359,0,npoints),np.linspace(-90, 90,npoints), - unit=u.deg, frame='icrs'), - SkyCoord(np.linspace(-3,3,npoints),np.linspace(-90, 90,npoints), - unit=(u.rad, u.deg), frame='barycentricmeanecliptic')]: - for sc2 in [SkyCoord(5*u.deg,10*u.deg), - SkyCoord(np.linspace(0, 359, npoints), np.linspace(-90, 90, npoints), - unit=u.deg, frame='galactic')]: + npoints = 7 # How many points when doing vectors of SkyCoords + for sc1 in [ + SkyCoord(0 * u.deg, -90 * u.deg), # South pole + SkyCoord(0 * u.deg, 90 * u.deg), # North pole + SkyCoord(1 * u.deg, 2 * u.deg), + SkyCoord( + np.linspace(0, 359, npoints), + np.linspace(-90, 90, npoints), + unit=u.deg, + frame="fk4", + ), + SkyCoord( + np.linspace(359, 0, npoints), + np.linspace(-90, 90, npoints), + unit=u.deg, + frame="icrs", + ), + SkyCoord( + np.linspace(-3, 3, npoints), + np.linspace(-90, 90, npoints), + unit=(u.rad, u.deg), + frame="barycentricmeanecliptic", + ), + ]: + for sc2 in [ + SkyCoord(5 * u.deg, 10 * u.deg), + SkyCoord( + np.linspace(0, 359, npoints), + np.linspace(-90, 90, npoints), + unit=u.deg, + frame="galactic", + ), + ]: # Find the displacement from sc1 to sc2, posang = sc1.position_angle(sc2) sep = sc1.separation(sc2) @@ -884,29 +963,29 @@ def test_directional_offset_by(): # Specific test cases # Go over the North pole a little way, and # over the South pole a long way, to get to same spot - sc1 = SkyCoord(0*u.deg, 89*u.deg) - for posang,sep in [(0*u.deg, 2*u.deg), (180*u.deg, 358*u.deg)]: + sc1 = SkyCoord(0 * u.deg, 89 * u.deg) + for posang, sep in [(0 * u.deg, 2 * u.deg), (180 * u.deg, 358 * u.deg)]: sc2 = sc1.directional_offset_by(posang, sep) assert allclose([sc2.ra.degree, sc2.dec.degree], [180, 89]) # Go twice as far to ensure that dec is actually changing # and that >360deg is supported - sc2 = sc1.directional_offset_by(posang, 2*sep) + sc2 = sc1.directional_offset_by(posang, 2 * sep) assert allclose([sc2.ra.degree, sc2.dec.degree], [180, 87]) # Verify that a separation of 180 deg in any direction gets to the antipode # and 360 deg returns to start - sc1 = SkyCoord(10*u.deg, 47*u.deg) + sc1 = SkyCoord(10 * u.deg, 47 * u.deg) for posang in np.linspace(0, 377, npoints): - sc2 = sc1.directional_offset_by(posang, 180*u.deg) + sc2 = sc1.directional_offset_by(posang, 180 * u.deg) assert allclose([sc2.ra.degree, sc2.dec.degree], [190, -47]) - sc2 = sc1.directional_offset_by(posang, 360*u.deg) + sc2 = sc1.directional_offset_by(posang, 360 * u.deg) assert allclose([sc2.ra.degree, sc2.dec.degree], [10, 47]) # Verify that a 90 degree posang, which means East # corresponds to an increase in RA, by ~separation/cos(dec) and # a slight convergence to equator - sc1 = SkyCoord(10*u.deg, 60*u.deg) - sc2 = sc1.directional_offset_by(90*u.deg, 1.0*u.deg) + sc1 = SkyCoord(10 * u.deg, 60 * u.deg) + sc2 = sc1.directional_offset_by(90 * u.deg, 1.0 * u.deg) assert 11.9 < sc2.ra.degree < 12.0 assert 59.9 < sc2.dec.degree < 60.0 @@ -922,10 +1001,10 @@ def test_table_to_coord(): from astropy.table import Column, Table t = Table() - t.add_column(Column(data=[1, 2, 3], name='ra', unit=u.deg)) - t.add_column(Column(data=[4, 5, 6], name='dec', unit=u.deg)) + t.add_column(Column(data=[1, 2, 3], name="ra", unit=u.deg)) + t.add_column(Column(data=[4, 5, 6], name="dec", unit=u.deg)) - c = SkyCoord(t['ra'], t['dec']) + c = SkyCoord(t["ra"], t["dec"]) assert allclose(c.ra.to(u.deg), [1, 2, 3] * u.deg) assert allclose(c.dec.to(u.deg), [4, 5, 6] * u.deg) @@ -947,11 +1026,11 @@ def assert_quantities_allclose(coord, q1s, attrs): # Sets of inputs corresponding to Galactic frame base_unit_attr_sets = [ - ('spherical', u.karcsec, u.karcsec, u.kpc, Latitude, 'l', 'b', 'distance'), - ('unitspherical', u.karcsec, u.karcsec, None, Latitude, 'l', 'b', None), - ('physicsspherical', u.karcsec, u.karcsec, u.kpc, Angle, 'phi', 'theta', 'r'), - ('cartesian', u.km, u.km, u.km, u.Quantity, 'u', 'v', 'w'), - ('cylindrical', u.km, u.karcsec, u.km, Angle, 'rho', 'phi', 'z') + ("spherical", u.karcsec, u.karcsec, u.kpc, Latitude, "l", "b", "distance"), + ("unitspherical", u.karcsec, u.karcsec, None, Latitude, "l", "b", None), + ("physicsspherical", u.karcsec, u.karcsec, u.kpc, Angle, "phi", "theta", "r"), + ("cartesian", u.km, u.km, u.km, u.Quantity, "u", "v", "w"), + ("cylindrical", u.km, u.karcsec, u.km, Angle, "rho", "phi", "z"), ] units_attr_sets = [] @@ -964,173 +1043,298 @@ def assert_quantities_allclose(coord, q1s, attrs): c1 = np.array(c1) c2 = np.array(c2) c3 = np.array(c3) - units_attr_sets.append(base_unit_attr_set + (representation, c1, c2, c3)) -units_attr_args = ('repr_name', 'unit1', 'unit2', 'unit3', 'cls2', 'attr1', 'attr2', 'attr3', 'representation', 'c1', 'c2', 'c3') + units_attr_sets.append( + base_unit_attr_set + (representation, c1, c2, c3) + ) +units_attr_args = ( + "repr_name", + "unit1", + "unit2", + "unit3", + "cls2", + "attr1", + "attr2", + "attr3", + "representation", + "c1", + "c2", + "c3", +) -@pytest.mark.parametrize(units_attr_args, - [x for x in units_attr_sets if x[0] != 'unitspherical']) -def test_skycoord_three_components(repr_name, unit1, unit2, unit3, cls2, attr1, attr2, attr3, - representation, c1, c2, c3): +@pytest.mark.parametrize( + units_attr_args, [x for x in units_attr_sets if x[0] != "unitspherical"] +) +def test_skycoord_three_components( + repr_name, + unit1, + unit2, + unit3, + cls2, + attr1, + attr2, + attr3, + representation, + c1, + c2, + c3, +): """ Tests positional inputs using components (COMP1, COMP2, COMP3) and various representations. Use weird units and Galactic frame. """ - sc = SkyCoord(c1, c2, c3, unit=(unit1, unit2, unit3), - representation_type=representation, - frame=Galactic) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) - - sc = SkyCoord(1000*c1*u.Unit(unit1/1000), cls2(c2, unit=unit2), - 1000*c3*u.Unit(unit3/1000), frame=Galactic, - unit=(unit1, unit2, unit3), representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) + sc = SkyCoord( + c1, + c2, + c3, + unit=(unit1, unit2, unit3), + representation_type=representation, + frame=Galactic, + ) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) + + sc = SkyCoord( + 1000 * c1 * u.Unit(unit1 / 1000), + cls2(c2, unit=unit2), + 1000 * c3 * u.Unit(unit3 / 1000), + frame=Galactic, + unit=(unit1, unit2, unit3), + representation_type=representation, + ) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) kwargs = {attr3: c3} - sc = SkyCoord(c1, c2, unit=(unit1, unit2, unit3), - frame=Galactic, - representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) + sc = SkyCoord( + c1, + c2, + unit=(unit1, unit2, unit3), + frame=Galactic, + representation_type=representation, + **kwargs, + ) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) kwargs = {attr1: c1, attr2: c2, attr3: c3} - sc = SkyCoord(frame=Galactic, unit=(unit1, unit2, unit3), - representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) - - -@pytest.mark.parametrize(units_attr_args, - [x for x in units_attr_sets - if x[0] in ('spherical', 'unitspherical')]) -def test_skycoord_spherical_two_components(repr_name, unit1, unit2, unit3, cls2, - attr1, attr2, attr3, representation, c1, c2, c3): + sc = SkyCoord( + frame=Galactic, + unit=(unit1, unit2, unit3), + representation_type=representation, + **kwargs, + ) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) + + +@pytest.mark.parametrize( + units_attr_args, + [x for x in units_attr_sets if x[0] in ("spherical", "unitspherical")], +) +def test_skycoord_spherical_two_components( + repr_name, + unit1, + unit2, + unit3, + cls2, + attr1, + attr2, + attr3, + representation, + c1, + c2, + c3, +): """ Tests positional inputs using components (COMP1, COMP2) for spherical representations. Use weird units and Galactic frame. """ - sc = SkyCoord(c1, c2, unit=(unit1, unit2), frame=Galactic, - representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), - (attr1, attr2)) - - sc = SkyCoord(1000*c1*u.Unit(unit1/1000), cls2(c2, unit=unit2), - frame=Galactic, - unit=(unit1, unit2, unit3), representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), - (attr1, attr2)) + sc = SkyCoord( + c1, c2, unit=(unit1, unit2), frame=Galactic, representation_type=representation + ) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) + + sc = SkyCoord( + 1000 * c1 * u.Unit(unit1 / 1000), + cls2(c2, unit=unit2), + frame=Galactic, + unit=(unit1, unit2, unit3), + representation_type=representation, + ) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) kwargs = {attr1: c1, attr2: c2} - sc = SkyCoord(frame=Galactic, unit=(unit1, unit2), - representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), - (attr1, attr2)) + sc = SkyCoord( + frame=Galactic, + unit=(unit1, unit2), + representation_type=representation, + **kwargs, + ) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) -@pytest.mark.parametrize(units_attr_args, - [x for x in units_attr_sets if x[0] != 'unitspherical']) -def test_galactic_three_components(repr_name, unit1, unit2, unit3, cls2, attr1, attr2, attr3, - representation, c1, c2, c3): +@pytest.mark.parametrize( + units_attr_args, [x for x in units_attr_sets if x[0] != "unitspherical"] +) +def test_galactic_three_components( + repr_name, + unit1, + unit2, + unit3, + cls2, + attr1, + attr2, + attr3, + representation, + c1, + c2, + c3, +): """ Tests positional inputs using components (COMP1, COMP2, COMP3) and various representations. Use weird units and Galactic frame. """ - sc = Galactic(1000*c1*u.Unit(unit1/1000), cls2(c2, unit=unit2), - 1000*c3*u.Unit(unit3/1000), representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) - - kwargs = {attr3: c3*unit3} - sc = Galactic(c1*unit1, c2*unit2, - representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) - - kwargs = {attr1: c1*unit1, attr2: c2*unit2, attr3: c3*unit3} + sc = Galactic( + 1000 * c1 * u.Unit(unit1 / 1000), + cls2(c2, unit=unit2), + 1000 * c3 * u.Unit(unit3 / 1000), + representation_type=representation, + ) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) + + kwargs = {attr3: c3 * unit3} + sc = Galactic(c1 * unit1, c2 * unit2, representation_type=representation, **kwargs) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) + + kwargs = {attr1: c1 * unit1, attr2: c2 * unit2, attr3: c3 * unit3} sc = Galactic(representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2, c3*unit3), - (attr1, attr2, attr3)) + assert_quantities_allclose( + sc, (c1 * unit1, c2 * unit2, c3 * unit3), (attr1, attr2, attr3) + ) -@pytest.mark.parametrize(units_attr_args, - [x for x in units_attr_sets - if x[0] in ('spherical', 'unitspherical')]) -def test_galactic_spherical_two_components(repr_name, unit1, unit2, unit3, cls2, - attr1, attr2, attr3, representation, c1, c2, c3): +@pytest.mark.parametrize( + units_attr_args, + [x for x in units_attr_sets if x[0] in ("spherical", "unitspherical")], +) +def test_galactic_spherical_two_components( + repr_name, + unit1, + unit2, + unit3, + cls2, + attr1, + attr2, + attr3, + representation, + c1, + c2, + c3, +): """ Tests positional inputs using components (COMP1, COMP2) for spherical representations. Use weird units and Galactic frame. """ - sc = Galactic(1000*c1*u.Unit(unit1/1000), cls2(c2, unit=unit2), representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), (attr1, attr2)) + sc = Galactic( + 1000 * c1 * u.Unit(unit1 / 1000), + cls2(c2, unit=unit2), + representation_type=representation, + ) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) - sc = Galactic(c1*unit1, c2*unit2, representation_type=representation) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), (attr1, attr2)) + sc = Galactic(c1 * unit1, c2 * unit2, representation_type=representation) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) - kwargs = {attr1: c1*unit1, attr2: c2*unit2} + kwargs = {attr1: c1 * unit1, attr2: c2 * unit2} sc = Galactic(representation_type=representation, **kwargs) - assert_quantities_allclose(sc, (c1*unit1, c2*unit2), (attr1, attr2)) + assert_quantities_allclose(sc, (c1 * unit1, c2 * unit2), (attr1, attr2)) -@pytest.mark.parametrize(('repr_name', 'unit1', 'unit2', 'unit3', 'cls2', 'attr1', 'attr2', 'attr3'), - [x for x in base_unit_attr_sets if x[0] != 'unitspherical']) -def test_skycoord_coordinate_input(repr_name, unit1, unit2, unit3, cls2, attr1, attr2, attr3): +@pytest.mark.parametrize( + ("repr_name", "unit1", "unit2", "unit3", "cls2", "attr1", "attr2", "attr3"), + [x for x in base_unit_attr_sets if x[0] != "unitspherical"], +) +def test_skycoord_coordinate_input( + repr_name, unit1, unit2, unit3, cls2, attr1, attr2, attr3 +): c1, c2, c3 = 1, 2, 3 - sc = SkyCoord([(c1, c2, c3)], unit=(unit1, unit2, unit3), representation_type=repr_name, - frame='galactic') - assert_quantities_allclose(sc, ([c1]*unit1, [c2]*unit2, [c3]*unit3), (attr1, attr2, attr3)) - - c1, c2, c3 = 1*unit1, 2*unit2, 3*unit3 - sc = SkyCoord([(c1, c2, c3)], representation_type=repr_name, frame='galactic') - assert_quantities_allclose(sc, ([1]*unit1, [2]*unit2, [3]*unit3), (attr1, attr2, attr3)) + sc = SkyCoord( + [(c1, c2, c3)], + unit=(unit1, unit2, unit3), + representation_type=repr_name, + frame="galactic", + ) + assert_quantities_allclose( + sc, ([c1] * unit1, [c2] * unit2, [c3] * unit3), (attr1, attr2, attr3) + ) + + c1, c2, c3 = 1 * unit1, 2 * unit2, 3 * unit3 + sc = SkyCoord([(c1, c2, c3)], representation_type=repr_name, frame="galactic") + assert_quantities_allclose( + sc, ([1] * unit1, [2] * unit2, [3] * unit3), (attr1, attr2, attr3) + ) def test_skycoord_string_coordinate_input(): - sc = SkyCoord('01 02 03 +02 03 04', unit='deg', representation_type='unitspherical') - assert_quantities_allclose(sc, (Angle('01:02:03', unit='deg'), - Angle('02:03:04', unit='deg')), - ('ra', 'dec')) - sc = SkyCoord(['01 02 03 +02 03 04'], unit='deg', representation_type='unitspherical') - assert_quantities_allclose(sc, (Angle(['01:02:03'], unit='deg'), - Angle(['02:03:04'], unit='deg')), - ('ra', 'dec')) + sc = SkyCoord("01 02 03 +02 03 04", unit="deg", representation_type="unitspherical") + assert_quantities_allclose( + sc, + (Angle("01:02:03", unit="deg"), Angle("02:03:04", unit="deg")), + ("ra", "dec"), + ) + sc = SkyCoord( + ["01 02 03 +02 03 04"], unit="deg", representation_type="unitspherical" + ) + assert_quantities_allclose( + sc, + (Angle(["01:02:03"], unit="deg"), Angle(["02:03:04"], unit="deg")), + ("ra", "dec"), + ) def test_units(): - sc = SkyCoord(1, 2, 3, unit='m', representation_type='cartesian') # All get meters + sc = SkyCoord(1, 2, 3, unit="m", representation_type="cartesian") # All get meters assert sc.x.unit is u.m assert sc.y.unit is u.m assert sc.z.unit is u.m - sc = SkyCoord(1, 2*u.km, 3, unit='m', representation_type='cartesian') # All get u.m + # All get u.m + sc = SkyCoord(1, 2 * u.km, 3, unit="m", representation_type="cartesian") assert sc.x.unit is u.m assert sc.y.unit is u.m assert sc.z.unit is u.m - sc = SkyCoord(1, 2, 3, unit=u.m, representation_type='cartesian') # All get u.m + sc = SkyCoord(1, 2, 3, unit=u.m, representation_type="cartesian") # All get u.m assert sc.x.unit is u.m assert sc.y.unit is u.m assert sc.z.unit is u.m - sc = SkyCoord(1, 2, 3, unit='m, km, pc', representation_type='cartesian') - assert_quantities_allclose(sc, (1*u.m, 2*u.km, 3*u.pc), ('x', 'y', 'z')) + sc = SkyCoord(1, 2, 3, unit="m, km, pc", representation_type="cartesian") + assert_quantities_allclose(sc, (1 * u.m, 2 * u.km, 3 * u.pc), ("x", "y", "z")) with pytest.raises(u.UnitsError) as err: - SkyCoord(1, 2, 3, unit=(u.m, u.m), representation_type='cartesian') - assert 'should have matching physical types' in str(err.value) + SkyCoord(1, 2, 3, unit=(u.m, u.m), representation_type="cartesian") + assert "should have matching physical types" in str(err.value) - SkyCoord(1, 2, 3, unit=(u.m, u.km, u.pc), representation_type='cartesian') - assert_quantities_allclose(sc, (1*u.m, 2*u.km, 3*u.pc), ('x', 'y', 'z')) + SkyCoord(1, 2, 3, unit=(u.m, u.km, u.pc), representation_type="cartesian") + assert_quantities_allclose(sc, (1 * u.m, 2 * u.km, 3 * u.pc), ("x", "y", "z")) @pytest.mark.xfail def test_units_known_fail(): # should fail but doesn't => corner case oddity with pytest.raises(u.UnitsError): - SkyCoord(1, 2, 3, unit=u.deg, representation_type='spherical') + SkyCoord(1, 2, 3, unit=u.deg, representation_type="spherical") def test_nodata_failure(): @@ -1138,35 +1342,38 @@ def test_nodata_failure(): SkyCoord() -@pytest.mark.parametrize(('mode', 'origin'), [('wcs', 0), - ('all', 0), - ('all', 1)]) +@pytest.mark.parametrize(("mode", "origin"), [("wcs", 0), ("all", 0), ("all", 1)]) def test_wcs_methods(mode, origin): from astropy.utils.data import get_pkg_data_contents from astropy.wcs import WCS from astropy.wcs.utils import pixel_to_skycoord - header = get_pkg_data_contents('../../wcs/tests/data/maps/1904-66_TAN.hdr', encoding='binary') + header = get_pkg_data_contents( + "../../wcs/tests/data/maps/1904-66_TAN.hdr", encoding="binary" + ) wcs = WCS(header) - ref = SkyCoord(0.1 * u.deg, -89. * u.deg, frame='icrs') + ref = SkyCoord(0.1 * u.deg, -89.0 * u.deg, frame="icrs") xp, yp = ref.to_pixel(wcs, mode=mode, origin=origin) # WCS is in FK5 so we need to transform back to ICRS - new = pixel_to_skycoord(xp, yp, wcs, mode=mode, origin=origin).transform_to('icrs') + new = pixel_to_skycoord(xp, yp, wcs, mode=mode, origin=origin).transform_to("icrs") assert_allclose(new.ra.degree, ref.ra.degree) assert_allclose(new.dec.degree, ref.dec.degree) # also try to round-trip with `from_pixel` - scnew = SkyCoord.from_pixel(xp, yp, wcs, mode=mode, origin=origin).transform_to('icrs') + scnew = SkyCoord.from_pixel(xp, yp, wcs, mode=mode, origin=origin).transform_to( + "icrs" + ) assert_allclose(scnew.ra.degree, ref.ra.degree) assert_allclose(scnew.dec.degree, ref.dec.degree) # Also make sure the right type comes out class SkyCoord2(SkyCoord): pass + scnew2 = SkyCoord2.from_pixel(xp, yp, wcs, mode=mode, origin=origin) assert scnew.__class__ is SkyCoord assert scnew2.__class__ is SkyCoord2 @@ -1179,40 +1386,40 @@ def test_frame_attr_transform_inherit(): """ c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK5) c2 = c.transform_to(FK4) - assert c2.equinox.value == 'B1950.000' - assert c2.obstime.value == 'B1950.000' + assert c2.equinox.value == "B1950.000" + assert c2.obstime.value == "B1950.000" - c2 = c.transform_to(FK4(equinox='J1975', obstime='J1980')) - assert c2.equinox.value == 'J1975.000' - assert c2.obstime.value == 'J1980.000' + c2 = c.transform_to(FK4(equinox="J1975", obstime="J1980")) + assert c2.equinox.value == "J1975.000" + assert c2.obstime.value == "J1980.000" c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK4) c2 = c.transform_to(FK5) - assert c2.equinox.value == 'J2000.000' + assert c2.equinox.value == "J2000.000" assert c2.obstime is None - c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK4, obstime='J1980') + c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK4, obstime="J1980") c2 = c.transform_to(FK5) - assert c2.equinox.value == 'J2000.000' - assert c2.obstime.value == 'J1980.000' + assert c2.equinox.value == "J2000.000" + assert c2.obstime.value == "J1980.000" - c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK4, equinox='J1975', obstime='J1980') + c = SkyCoord(1 * u.deg, 2 * u.deg, frame=FK4, equinox="J1975", obstime="J1980") c2 = c.transform_to(FK5) - assert c2.equinox.value == 'J1975.000' - assert c2.obstime.value == 'J1980.000' + assert c2.equinox.value == "J1975.000" + assert c2.obstime.value == "J1980.000" - c2 = c.transform_to(FK5(equinox='J1990')) - assert c2.equinox.value == 'J1990.000' - assert c2.obstime.value == 'J1980.000' + c2 = c.transform_to(FK5(equinox="J1990")) + assert c2.equinox.value == "J1990.000" + assert c2.obstime.value == "J1980.000" # The work-around for #5722 - c = SkyCoord(1 * u.deg, 2 * u.deg, frame='fk5') - c1 = SkyCoord(1 * u.deg, 2 * u.deg, frame='fk5', equinox='B1950.000') + c = SkyCoord(1 * u.deg, 2 * u.deg, frame="fk5") + c1 = SkyCoord(1 * u.deg, 2 * u.deg, frame="fk5", equinox="B1950.000") c2 = c1.transform_to(c) assert not c2.is_equivalent_frame(c) # counterintuitive, but documented - assert c2.equinox.value == 'B1950.000' + assert c2.equinox.value == "B1950.000" c3 = c1.transform_to(c, merge_attributes=False) - assert c3.equinox.value == 'J2000.000' + assert c3.equinox.value == "J2000.000" assert c3.is_equivalent_frame(c) @@ -1221,8 +1428,15 @@ def test_deepcopy(): c2 = copy.copy(c1) c3 = copy.deepcopy(c1) - c4 = SkyCoord([1, 2] * u.m, [2, 3] * u.m, [3, 4] * u.m, representation_type='cartesian', frame='fk5', - obstime='J1999.9', equinox='J1988.8') + c4 = SkyCoord( + [1, 2] * u.m, + [2, 3] * u.m, + [3, 4] * u.m, + representation_type="cartesian", + frame="fk5", + obstime="J1999.9", + equinox="J1988.8", + ) c5 = copy.deepcopy(c4) assert np.all(c5.x == c4.x) # and y and z assert c5.frame.name == c4.frame.name @@ -1232,7 +1446,7 @@ def test_deepcopy(): def test_no_copy(): - c1 = SkyCoord(np.arange(10.) * u.hourangle, np.arange(20., 30.) * u.deg) + c1 = SkyCoord(np.arange(10.0) * u.hourangle, np.arange(20.0, 30.0) * u.deg) c2 = SkyCoord(c1, copy=False) # Note: c1.ra and c2.ra will *not* share memory, as these are recalculated # to be in "preferred" units. See discussion in #4883. @@ -1250,7 +1464,7 @@ def test_immutable(): assert c1.foo == 42 -@pytest.mark.skipif(not HAS_SCIPY, reason='Requires scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") def test_search_around(): """ Test the search_around_* methods @@ -1261,40 +1475,42 @@ def test_search_around(): from astropy.utils import NumpyRNGContext with NumpyRNGContext(987654321): - sc1 = SkyCoord(np.random.rand(20) * 360.*u.degree, - (np.random.rand(20) * 180. - 90.)*u.degree) - sc2 = SkyCoord(np.random.rand(100) * 360. * u.degree, - (np.random.rand(100) * 180. - 90.)*u.degree) + sc1 = SkyCoord( + np.random.rand(20) * 360.0 * u.degree, + (np.random.rand(20) * 180.0 - 90.0) * u.degree, + ) + sc2 = SkyCoord( + np.random.rand(100) * 360.0 * u.degree, + (np.random.rand(100) * 180.0 - 90.0) * u.degree, + ) - sc1ds = SkyCoord(ra=sc1.ra, dec=sc1.dec, distance=np.random.rand(20)*u.kpc) - sc2ds = SkyCoord(ra=sc2.ra, dec=sc2.dec, distance=np.random.rand(100)*u.kpc) + sc1ds = SkyCoord(ra=sc1.ra, dec=sc1.dec, distance=np.random.rand(20) * u.kpc) + sc2ds = SkyCoord(ra=sc2.ra, dec=sc2.dec, distance=np.random.rand(100) * u.kpc) - idx1_sky, idx2_sky, d2d_sky, d3d_sky = sc1.search_around_sky(sc2, 10*u.deg) - idx1_3d, idx2_3d, d2d_3d, d3d_3d = sc1ds.search_around_3d(sc2ds, 250*u.pc) + idx1_sky, idx2_sky, d2d_sky, d3d_sky = sc1.search_around_sky(sc2, 10 * u.deg) + idx1_3d, idx2_3d, d2d_3d, d3d_3d = sc1ds.search_around_3d(sc2ds, 250 * u.pc) def test_init_with_frame_instance_keyword(): - # Frame instance - c1 = SkyCoord(3 * u.deg, 4 * u.deg, - frame=FK5(equinox='J2010')) - assert c1.equinox == Time('J2010') + c1 = SkyCoord(3 * u.deg, 4 * u.deg, frame=FK5(equinox="J2010")) + assert c1.equinox == Time("J2010") # Frame instance with data (data gets ignored) - c2 = SkyCoord(3 * u.deg, 4 * u.deg, - frame=FK5(1. * u.deg, 2 * u.deg, - equinox='J2010')) - assert c2.equinox == Time('J2010') + c2 = SkyCoord( + 3 * u.deg, 4 * u.deg, frame=FK5(1.0 * u.deg, 2 * u.deg, equinox="J2010") + ) + assert c2.equinox == Time("J2010") assert allclose(c2.ra.degree, 3) assert allclose(c2.dec.degree, 4) # SkyCoord instance c3 = SkyCoord(3 * u.deg, 4 * u.deg, frame=c1) - assert c3.equinox == Time('J2010') + assert c3.equinox == Time("J2010") # Check duplicate arguments with pytest.raises(ValueError) as err: - c = SkyCoord(3 * u.deg, 4 * u.deg, frame=FK5(equinox='J2010'), equinox='J2001') + c = SkyCoord(3 * u.deg, 4 * u.deg, frame=FK5(equinox="J2010"), equinox="J2001") assert "Cannot specify frame attribute 'equinox'" in str(err.value) @@ -1304,92 +1520,90 @@ def test_guess_from_table(): tab = Table() with NumpyRNGContext(987654321): - tab.add_column(Column(data=np.random.rand(10), unit='deg', name='RA[J2000]')) - tab.add_column(Column(data=np.random.rand(10), unit='deg', name='DEC[J2000]')) + tab.add_column(Column(data=np.random.rand(10), unit="deg", name="RA[J2000]")) + tab.add_column(Column(data=np.random.rand(10), unit="deg", name="DEC[J2000]")) sc = SkyCoord.guess_from_table(tab) - npt.assert_array_equal(sc.ra.deg, tab['RA[J2000]']) - npt.assert_array_equal(sc.dec.deg, tab['DEC[J2000]']) + npt.assert_array_equal(sc.ra.deg, tab["RA[J2000]"]) + npt.assert_array_equal(sc.dec.deg, tab["DEC[J2000]"]) # try without units in the table - tab['RA[J2000]'].unit = None - tab['DEC[J2000]'].unit = None + tab["RA[J2000]"].unit = None + tab["DEC[J2000]"].unit = None # should fail if not given explicitly with pytest.raises(u.UnitsError): sc2 = SkyCoord.guess_from_table(tab) # but should work if provided sc2 = SkyCoord.guess_from_table(tab, unit=u.deg) - npt.assert_array_equal(sc2.ra.deg, tab['RA[J2000]']) - npt.assert_array_equal(sc2.dec.deg, tab['DEC[J2000]']) + npt.assert_array_equal(sc2.ra.deg, tab["RA[J2000]"]) + npt.assert_array_equal(sc2.dec.deg, tab["DEC[J2000]"]) # should fail if two options are available - ambiguity bad! - tab.add_column(Column(data=np.random.rand(10), name='RA_J1900')) + tab.add_column(Column(data=np.random.rand(10), name="RA_J1900")) with pytest.raises(ValueError) as excinfo: SkyCoord.guess_from_table(tab, unit=u.deg) - assert 'J1900' in excinfo.value.args[0] and 'J2000' in excinfo.value.args[0] + assert "J1900" in excinfo.value.args[0] and "J2000" in excinfo.value.args[0] - tab.remove_column('RA_J1900') - tab['RA[J2000]'].unit = u.deg - tab['DEC[J2000]'].unit = u.deg + tab.remove_column("RA_J1900") + tab["RA[J2000]"].unit = u.deg + tab["DEC[J2000]"].unit = u.deg # but should succeed if the ambiguity can be broken b/c one of the matches # is the name of a different component - tab.add_column(Column(data=np.random.rand(10)*u.mas/u.yr, - name='pm_ra_cosdec')) - tab.add_column(Column(data=np.random.rand(10)*u.mas/u.yr, - name='pm_dec')) + tab.add_column(Column(data=np.random.rand(10) * u.mas / u.yr, name="pm_ra_cosdec")) + tab.add_column(Column(data=np.random.rand(10) * u.mas / u.yr, name="pm_dec")) sc3 = SkyCoord.guess_from_table(tab) - assert u.allclose(sc3.ra, tab['RA[J2000]']) - assert u.allclose(sc3.dec, tab['DEC[J2000]']) - assert u.allclose(sc3.pm_ra_cosdec, tab['pm_ra_cosdec']) - assert u.allclose(sc3.pm_dec, tab['pm_dec']) + assert u.allclose(sc3.ra, tab["RA[J2000]"]) + assert u.allclose(sc3.dec, tab["DEC[J2000]"]) + assert u.allclose(sc3.pm_ra_cosdec, tab["pm_ra_cosdec"]) + assert u.allclose(sc3.pm_dec, tab["pm_dec"]) # should fail if stuff doesn't have proper units - tab['RA[J2000]'].unit = None - tab['DEC[J2000]'].unit = None + tab["RA[J2000]"].unit = None + tab["DEC[J2000]"].unit = None with pytest.raises(u.UnitTypeError, match="no unit was given."): SkyCoord.guess_from_table(tab) - tab.remove_column('pm_ra_cosdec') - tab.remove_column('pm_dec') + tab.remove_column("pm_ra_cosdec") + tab.remove_column("pm_dec") # should also fail if user specifies something already in the table, but # should succeed even if the user has to give one of the components with pytest.raises(ValueError): - SkyCoord.guess_from_table(tab, ra=tab['RA[J2000]'], unit=u.deg) + SkyCoord.guess_from_table(tab, ra=tab["RA[J2000]"], unit=u.deg) - oldra = tab['RA[J2000]'] - tab.remove_column('RA[J2000]') + oldra = tab["RA[J2000]"] + tab.remove_column("RA[J2000]") sc3 = SkyCoord.guess_from_table(tab, ra=oldra, unit=u.deg) npt.assert_array_equal(sc3.ra.deg, oldra) - npt.assert_array_equal(sc3.dec.deg, tab['DEC[J2000]']) + npt.assert_array_equal(sc3.dec.deg, tab["DEC[J2000]"]) # check a few non-ICRS/spherical systems x, y, z = np.arange(3).reshape(3, 1) * u.pc l, b = np.arange(2).reshape(2, 1) * u.deg - tabcart = Table([x, y, z], names=('x', 'y', 'z')) - tabgal = Table([b, l], names=('b', 'l')) + tabcart = Table([x, y, z], names=("x", "y", "z")) + tabgal = Table([b, l], names=("b", "l")) - sc_cart = SkyCoord.guess_from_table(tabcart, representation_type='cartesian') + sc_cart = SkyCoord.guess_from_table(tabcart, representation_type="cartesian") npt.assert_array_equal(sc_cart.x, x) npt.assert_array_equal(sc_cart.y, y) npt.assert_array_equal(sc_cart.z, z) - sc_gal = SkyCoord.guess_from_table(tabgal, frame='galactic') + sc_gal = SkyCoord.guess_from_table(tabgal, frame="galactic") npt.assert_array_equal(sc_gal.l, l) npt.assert_array_equal(sc_gal.b, b) # also try some column names that *end* with the attribute name - tabgal['b'].name = 'gal_b' - tabgal['l'].name = 'gal_l' - SkyCoord.guess_from_table(tabgal, frame='galactic') + tabgal["b"].name = "gal_b" + tabgal["l"].name = "gal_l" + SkyCoord.guess_from_table(tabgal, frame="galactic") - tabgal['gal_b'].name = 'blob' - tabgal['gal_l'].name = 'central' + tabgal["gal_b"].name = "blob" + tabgal["gal_l"].name = "central" with pytest.raises(ValueError): - SkyCoord.guess_from_table(tabgal, frame='galactic') + SkyCoord.guess_from_table(tabgal, frame="galactic") def test_skycoord_list_creation(): @@ -1397,12 +1611,12 @@ def test_skycoord_list_creation(): Test that SkyCoord can be created in a reasonable way with lists of SkyCoords (regression for #2702) """ - sc = SkyCoord(ra=[1, 2, 3]*u.deg, dec=[4, 5, 6]*u.deg) + sc = SkyCoord(ra=[1, 2, 3] * u.deg, dec=[4, 5, 6] * u.deg) sc0 = sc[0] sc2 = sc[2] scnew = SkyCoord([sc0, sc2]) - assert np.all(scnew.ra == [1, 3]*u.deg) - assert np.all(scnew.dec == [4, 6]*u.deg) + assert np.all(scnew.ra == [1, 3] * u.deg) + assert np.all(scnew.dec == [4, 6] * u.deg) # also check ranges sc01 = sc[:2] @@ -1411,40 +1625,40 @@ def test_skycoord_list_creation(): assert np.all(scnew2.dec == sc.dec) # now try with a mix of skycoord, frame, and repr objects - frobj = ICRS(2*u.deg, 5*u.deg) - reprobj = UnitSphericalRepresentation(3*u.deg, 6*u.deg) + frobj = ICRS(2 * u.deg, 5 * u.deg) + reprobj = UnitSphericalRepresentation(3 * u.deg, 6 * u.deg) scnew3 = SkyCoord([sc0, frobj, reprobj]) assert np.all(scnew3.ra == sc.ra) assert np.all(scnew3.dec == sc.dec) # should *fail* if different frame attributes or types are passed in - scfk5_j2000 = SkyCoord(1*u.deg, 4*u.deg, frame='fk5') + scfk5_j2000 = SkyCoord(1 * u.deg, 4 * u.deg, frame="fk5") with pytest.raises(ValueError): SkyCoord([sc0, scfk5_j2000]) - scfk5_j2010 = SkyCoord(1*u.deg, 4*u.deg, frame='fk5', equinox='J2010') + scfk5_j2010 = SkyCoord(1 * u.deg, 4 * u.deg, frame="fk5", equinox="J2010") with pytest.raises(ValueError): SkyCoord([scfk5_j2000, scfk5_j2010]) # but they should inherit if they're all consistent - scfk5_2_j2010 = SkyCoord(2*u.deg, 5*u.deg, frame='fk5', equinox='J2010') - scfk5_3_j2010 = SkyCoord(3*u.deg, 6*u.deg, frame='fk5', equinox='J2010') + scfk5_2_j2010 = SkyCoord(2 * u.deg, 5 * u.deg, frame="fk5", equinox="J2010") + scfk5_3_j2010 = SkyCoord(3 * u.deg, 6 * u.deg, frame="fk5", equinox="J2010") scnew4 = SkyCoord([scfk5_j2010, scfk5_2_j2010, scfk5_3_j2010]) assert np.all(scnew4.ra == sc.ra) assert np.all(scnew4.dec == sc.dec) - assert scnew4.equinox == Time('J2010') + assert scnew4.equinox == Time("J2010") def test_nd_skycoord_to_string(): - c = SkyCoord(np.ones((2, 2)), 1, unit=('deg', 'deg')) + c = SkyCoord(np.ones((2, 2)), 1, unit=("deg", "deg")) ts = c.to_string() assert np.all(ts.shape == c.shape) - assert np.all(ts == '1 1') + assert np.all(ts == "1 1") def test_equiv_skycoord(): - sci1 = SkyCoord(1*u.deg, 2*u.deg, frame='icrs') - sci2 = SkyCoord(1*u.deg, 3*u.deg, frame='icrs') + sci1 = SkyCoord(1 * u.deg, 2 * u.deg, frame="icrs") + sci2 = SkyCoord(1 * u.deg, 3 * u.deg, frame="icrs") assert sci1.is_equivalent_frame(sci1) assert sci1.is_equivalent_frame(sci2) @@ -1453,26 +1667,28 @@ def test_equiv_skycoord(): with pytest.raises(TypeError): sci1.is_equivalent_frame(10) - scf1 = SkyCoord(1*u.deg, 2*u.deg, frame='fk5') - scf2 = SkyCoord(1*u.deg, 2*u.deg, frame='fk5', equinox='J2005') + scf1 = SkyCoord(1 * u.deg, 2 * u.deg, frame="fk5") + scf2 = SkyCoord(1 * u.deg, 2 * u.deg, frame="fk5", equinox="J2005") # obstime is *not* an FK5 attribute, but we still want scf1 and scf3 to come # to come out different because they're part of SkyCoord - scf3 = SkyCoord(1*u.deg, 2*u.deg, frame='fk5', obstime='J2005') + scf3 = SkyCoord(1 * u.deg, 2 * u.deg, frame="fk5", obstime="J2005") assert scf1.is_equivalent_frame(scf1) assert not scf1.is_equivalent_frame(sci1) assert scf1.is_equivalent_frame(FK5()) assert not scf1.is_equivalent_frame(scf2) - assert scf2.is_equivalent_frame(FK5(equinox='J2005')) + assert scf2.is_equivalent_frame(FK5(equinox="J2005")) assert not scf3.is_equivalent_frame(scf1) - assert not scf3.is_equivalent_frame(FK5(equinox='J2005')) + assert not scf3.is_equivalent_frame(FK5(equinox="J2005")) def test_equiv_skycoord_with_extra_attrs(): """Regression test for #10658.""" # GCRS has a CartesianRepresentationAttribute called obsgeoloc - gcrs = GCRS(1*u.deg, 2*u.deg, obsgeoloc=CartesianRepresentation([1, 2, 3], unit=u.m)) + gcrs = GCRS( + 1 * u.deg, 2 * u.deg, obsgeoloc=CartesianRepresentation([1, 2, 3], unit=u.m) + ) # Create a SkyCoord where obsgeoloc tags along as an extra attribute sc1 = SkyCoord(gcrs).transform_to(ICRS) # Now create a SkyCoord with an equivalent frame but without the extra attribute @@ -1486,31 +1702,33 @@ def test_equiv_skycoord_with_extra_attrs(): def test_constellations(): # the actual test for accuracy is in test_funcs - this is just meant to make # sure we get sensible answers - sc = SkyCoord(135*u.deg, 65*u.deg) - assert sc.get_constellation() == 'Ursa Major' - assert sc.get_constellation(short_name=True) == 'UMa' + sc = SkyCoord(135 * u.deg, 65 * u.deg) + assert sc.get_constellation() == "Ursa Major" + assert sc.get_constellation(short_name=True) == "UMa" - scs = SkyCoord([135]*2*u.deg, [65]*2*u.deg) - npt.assert_equal(scs.get_constellation(), ['Ursa Major']*2) - npt.assert_equal(scs.get_constellation(short_name=True), ['UMa']*2) + scs = SkyCoord([135] * 2 * u.deg, [65] * 2 * u.deg) + npt.assert_equal(scs.get_constellation(), ["Ursa Major"] * 2) + npt.assert_equal(scs.get_constellation(short_name=True), ["UMa"] * 2) @pytest.mark.remote_data def test_constellations_with_nameresolve(): - assert SkyCoord.from_name('And I').get_constellation(short_name=True) == 'And' + assert SkyCoord.from_name("And I").get_constellation(short_name=True) == "And" # you'd think "And ..." should be in Andromeda. But you'd be wrong. - assert SkyCoord.from_name('And VI').get_constellation() == 'Pegasus' + assert SkyCoord.from_name("And VI").get_constellation() == "Pegasus" # maybe it's because And VI isn't really a galaxy? - assert SkyCoord.from_name('And XXII').get_constellation() == 'Pisces' - assert SkyCoord.from_name('And XXX').get_constellation() == 'Cassiopeia' + assert SkyCoord.from_name("And XXII").get_constellation() == "Pisces" + assert SkyCoord.from_name("And XXX").get_constellation() == "Cassiopeia" # ok maybe not # ok, but at least some of the others do make sense... - assert SkyCoord.from_name('Coma Cluster').get_constellation(short_name=True) == 'Com' - assert SkyCoord.from_name('Orion Nebula').get_constellation() == 'Orion' - assert SkyCoord.from_name('Triangulum Galaxy').get_constellation() == 'Triangulum' + assert ( + SkyCoord.from_name("Coma Cluster").get_constellation(short_name=True) == "Com" + ) + assert SkyCoord.from_name("Orion Nebula").get_constellation() == "Orion" + assert SkyCoord.from_name("Triangulum Galaxy").get_constellation() == "Triangulum" def test_getitem_representation(): @@ -1519,37 +1737,42 @@ def test_getitem_representation(): from data representation. """ sc = SkyCoord([1, 1] * u.deg, [2, 2] * u.deg) - sc.representation_type = 'cartesian' + sc.representation_type = "cartesian" assert sc[0].representation_type is CartesianRepresentation def test_spherical_offsets_to_api(): - i00 = SkyCoord(0*u.arcmin, 0*u.arcmin, frame='icrs') + i00 = SkyCoord(0 * u.arcmin, 0 * u.arcmin, frame="icrs") - fk5 = SkyCoord(0*u.arcmin, 0*u.arcmin, frame='fk5') + fk5 = SkyCoord(0 * u.arcmin, 0 * u.arcmin, frame="fk5") with pytest.raises(ValueError): # different frames should fail i00.spherical_offsets_to(fk5) - i1deg = ICRS(1*u.deg, 1*u.deg) + i1deg = ICRS(1 * u.deg, 1 * u.deg) dra, ddec = i00.spherical_offsets_to(i1deg) - assert_allclose(dra, 1*u.deg) - assert_allclose(ddec, 1*u.deg) + assert_allclose(dra, 1 * u.deg) + assert_allclose(ddec, 1 * u.deg) # make sure an abbreviated array-based version of the above also works - i00s = SkyCoord([0]*4*u.arcmin, [0]*4*u.arcmin, frame='icrs') - i01s = SkyCoord([0]*4*u.arcmin, np.arange(4)*u.arcmin, frame='icrs') + i00s = SkyCoord([0] * 4 * u.arcmin, [0] * 4 * u.arcmin, frame="icrs") + i01s = SkyCoord([0] * 4 * u.arcmin, np.arange(4) * u.arcmin, frame="icrs") dra, ddec = i00s.spherical_offsets_to(i01s) - assert_allclose(dra, 0*u.arcmin) - assert_allclose(ddec, np.arange(4)*u.arcmin) - - -@pytest.mark.parametrize('frame', ['icrs', 'galactic']) -@pytest.mark.parametrize('comparison_data', [(0*u.arcmin, 1*u.arcmin), - (1*u.arcmin, 0*u.arcmin), - (1*u.arcmin, 1*u.arcmin)]) + assert_allclose(dra, 0 * u.arcmin) + assert_allclose(ddec, np.arange(4) * u.arcmin) + + +@pytest.mark.parametrize("frame", ["icrs", "galactic"]) +@pytest.mark.parametrize( + "comparison_data", + [ + (0 * u.arcmin, 1 * u.arcmin), + (1 * u.arcmin, 0 * u.arcmin), + (1 * u.arcmin, 1 * u.arcmin), + ], +) def test_spherical_offsets_roundtrip(frame, comparison_data): - i00 = SkyCoord(0*u.arcmin, 0*u.arcmin, frame=frame) + i00 = SkyCoord(0 * u.arcmin, 0 * u.arcmin, frame=frame) comparison = SkyCoord(*comparison_data, frame=frame) dlon, dlat = i00.spherical_offsets_to(comparison) @@ -1561,15 +1784,15 @@ def test_spherical_offsets_roundtrip(frame, comparison_data): # This reaches machine precision when only one component is changed, but for # the third parametrized case (both lon and lat change), the transformation # will have finite accuracy: - assert_allclose(i00_back.data.lon, i00.data.lon, atol=1e-10*u.rad) - assert_allclose(i00_back.data.lat, i00.data.lat, atol=1e-10*u.rad) + assert_allclose(i00_back.data.lon, i00.data.lon, atol=1e-10 * u.rad) + assert_allclose(i00_back.data.lat, i00.data.lat, atol=1e-10 * u.rad) # Test roundtripping the other direction: - init_c = SkyCoord(40.*u.deg, 40.*u.deg, frame=frame) - new_c = init_c.spherical_offsets_by(3.534*u.deg, 2.2134*u.deg) + init_c = SkyCoord(40.0 * u.deg, 40.0 * u.deg, frame=frame) + new_c = init_c.spherical_offsets_by(3.534 * u.deg, 2.2134 * u.deg) dlon, dlat = new_c.spherical_offsets_to(init_c) back_c = new_c.spherical_offsets_by(dlon, dlat) - assert init_c.separation(back_c) < 1e-10*u.deg + assert init_c.separation(back_c) < 1e-10 * u.deg def test_frame_attr_changes(): @@ -1580,9 +1803,9 @@ def test_frame_attr_changes(): frames are added or removed from the transform graph. This makes sure that everything continues to work consistently. """ - sc_before = SkyCoord(1*u.deg, 2*u.deg, frame='icrs') + sc_before = SkyCoord(1 * u.deg, 2 * u.deg, frame="icrs") - assert 'fakeattr' not in dir(sc_before) + assert "fakeattr" not in dir(sc_before) class FakeFrame(BaseCoordinateFrame): fakeattr = Attribute() @@ -1592,51 +1815,51 @@ class FakeFrame(BaseCoordinateFrame): transset = (ICRS, FakeFrame, lambda c, f: c) frame_transform_graph.add_transform(*transset) try: - assert 'fakeattr' in dir(sc_before) + assert "fakeattr" in dir(sc_before) assert sc_before.fakeattr is None - sc_after1 = SkyCoord(1*u.deg, 2*u.deg, frame='icrs') - assert 'fakeattr' in dir(sc_after1) + sc_after1 = SkyCoord(1 * u.deg, 2 * u.deg, frame="icrs") + assert "fakeattr" in dir(sc_after1) assert sc_after1.fakeattr is None - sc_after2 = SkyCoord(1*u.deg, 2*u.deg, frame='icrs', fakeattr=1) + sc_after2 = SkyCoord(1 * u.deg, 2 * u.deg, frame="icrs", fakeattr=1) assert sc_after2.fakeattr == 1 finally: frame_transform_graph.remove_transform(*transset) - assert 'fakeattr' not in dir(sc_before) - assert 'fakeattr' not in dir(sc_after1) - assert 'fakeattr' not in dir(sc_after2) + assert "fakeattr" not in dir(sc_before) + assert "fakeattr" not in dir(sc_after1) + assert "fakeattr" not in dir(sc_after2) def test_cache_clear_sc(): from astropy.coordinates import SkyCoord - i = SkyCoord(1*u.deg, 2*u.deg) + i = SkyCoord(1 * u.deg, 2 * u.deg) # Add an in frame units version of the rep to the cache. repr(i) - assert len(i.cache['representation']) == 2 + assert len(i.cache["representation"]) == 2 i.cache.clear() - assert len(i.cache['representation']) == 0 + assert len(i.cache["representation"]) == 0 def test_set_attribute_exceptions(): """Ensure no attrbute for any frame can be set directly. Though it is fine if the current frame does not have it.""" - sc = SkyCoord(1.*u.deg, 2.*u.deg, frame='fk5') - assert hasattr(sc.frame, 'equinox') + sc = SkyCoord(1.0 * u.deg, 2.0 * u.deg, frame="fk5") + assert hasattr(sc.frame, "equinox") with pytest.raises(AttributeError): - sc.equinox = 'B1950' + sc.equinox = "B1950" assert sc.relative_humidity is None sc.relative_humidity = 0.5 assert sc.relative_humidity == 0.5 - assert not hasattr(sc.frame, 'relative_humidity') + assert not hasattr(sc.frame, "relative_humidity") def test_extra_attributes(): @@ -1644,10 +1867,10 @@ def test_extra_attributes(): Regression test against #5743. """ - obstime_string = ['2017-01-01T00:00', '2017-01-01T00:10'] + obstime_string = ["2017-01-01T00:00", "2017-01-01T00:10"] obstime = Time(obstime_string) sc = SkyCoord([5, 10], [20, 30], unit=u.deg, obstime=obstime_string) - assert not hasattr(sc.frame, 'obstime') + assert not hasattr(sc.frame, "obstime") assert type(sc.obstime) is Time assert sc.obstime.shape == (2,) assert np.all(sc.obstime == obstime) @@ -1656,15 +1879,15 @@ def test_extra_attributes(): sc_1 = sc[1] assert sc_1.obstime == obstime[1] # Transforming to FK4 should use sc.obstime. - sc_fk4 = sc.transform_to('fk4') + sc_fk4 = sc.transform_to("fk4") assert np.all(sc_fk4.frame.obstime == obstime) # And transforming back should not loose it. - sc2 = sc_fk4.transform_to('icrs') - assert not hasattr(sc2.frame, 'obstime') + sc2 = sc_fk4.transform_to("icrs") + assert not hasattr(sc2.frame, "obstime") assert np.all(sc2.obstime == obstime) # Ensure obstime get taken from the SkyCoord if passed in directly. # (regression test for #5749). - sc3 = SkyCoord([0., 1.], [2., 3.], unit='deg', frame=sc) + sc3 = SkyCoord([0.0, 1.0], [2.0, 3.0], unit="deg", frame=sc) assert np.all(sc3.obstime == obstime) # Finally, check that we can delete such attributes. del sc3.obstime @@ -1674,22 +1897,25 @@ def test_extra_attributes(): def test_apply_space_motion(): # use this 12 year period because it's a multiple of 4 to avoid the quirks # of leap years while having 2 leap seconds in it - t1 = Time('2000-01-01T00:00') - t2 = Time('2012-01-01T00:00') + t1 = Time("2000-01-01T00:00") + t2 = Time("2012-01-01T00:00") # Check a very simple case first: - frame = ICRS(ra=10.*u.deg, dec=0*u.deg, - distance=10.*u.pc, - pm_ra_cosdec=0.1*u.deg/u.yr, - pm_dec=0*u.mas/u.yr, - radial_velocity=0*u.km/u.s) + frame = ICRS( + ra=10.0 * u.deg, + dec=0 * u.deg, + distance=10.0 * u.pc, + pm_ra_cosdec=0.1 * u.deg / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=0 * u.km / u.s, + ) # Cases that should work (just testing input for now): - c1 = SkyCoord(frame, obstime=t1, pressure=101*u.kPa) + c1 = SkyCoord(frame, obstime=t1, pressure=101 * u.kPa) with pytest.warns(ErfaWarning, match='ERFA function "pmsafe" yielded .*'): # warning raised due to high PM chosen above applied1 = c1.apply_space_motion(new_obstime=t2) - applied2 = c1.apply_space_motion(dt=12*u.year) + applied2 = c1.apply_space_motion(dt=12 * u.year) assert isinstance(applied1.frame, c1.frame.__class__) assert isinstance(applied2.frame, c1.frame.__class__) @@ -1704,24 +1930,28 @@ def test_apply_space_motion(): # there were 2 leap seconds between 2000 and 2010, so the difference in # the two forms of time evolution should be ~2 sec adt = np.abs(applied2.obstime - applied1.obstime) - assert 1.9*u.second < adt.to(u.second) < 2.1*u.second + assert 1.9 * u.second < adt.to(u.second) < 2.1 * u.second c2 = SkyCoord(frame) with pytest.warns(ErfaWarning, match='ERFA function "pmsafe" yielded .*'): # warning raised due to high PM chosen above - applied3 = c2.apply_space_motion(dt=6*u.year) + applied3 = c2.apply_space_motion(dt=6 * u.year) assert isinstance(applied3.frame, c1.frame.__class__) assert applied3.obstime is None # this should *not* be .6 deg due to space-motion on a sphere, but it # should be fairly close - assert 0.5*u.deg < applied3.ra-c1.ra < .7*u.deg + assert 0.5 * u.deg < applied3.ra - c1.ra < 0.7 * u.deg # the two cases should only match somewhat due to it being space motion, but # they should be at least this close - assert quantity_allclose(applied1.ra-c1.ra, (applied3.ra-c1.ra)*2, atol=1e-3*u.deg) + assert quantity_allclose( + applied1.ra - c1.ra, (applied3.ra - c1.ra) * 2, atol=1e-3 * u.deg + ) # but *not* this close - assert not quantity_allclose(applied1.ra-c1.ra, (applied3.ra-c1.ra)*2, atol=1e-4*u.deg) + assert not quantity_allclose( + applied1.ra - c1.ra, (applied3.ra - c1.ra) * 2, atol=1e-4 * u.deg + ) with pytest.raises(ValueError): c2.apply_space_motion(new_obstime=t2) @@ -1736,13 +1966,14 @@ class BlahBleeBlopFrame(BaseCoordinateFrame): # default_differential = SphericalDifferential _frame_specific_representation_info = { - 'spherical': [ - RepresentationMapping('lon', 'lon', 'recommended'), - RepresentationMapping('lat', 'lat', 'recommended'), - RepresentationMapping('distance', 'radius', 'recommended') + "spherical": [ + RepresentationMapping("lon", "lon", "recommended"), + RepresentationMapping("lat", "lat", "recommended"), + RepresentationMapping("distance", "radius", "recommended"), ] } - SkyCoord(lat=1*u.deg, lon=2*u.deg, frame=BlahBleeBlopFrame) + + SkyCoord(lat=1 * u.deg, lon=2 * u.deg, frame=BlahBleeBlopFrame) def test_user_friendly_pm_error(): @@ -1752,22 +1983,35 @@ def test_user_friendly_pm_error(): """ with pytest.raises(ValueError) as e: - SkyCoord(ra=150*u.deg, dec=-11*u.deg, - pm_ra=100*u.mas/u.yr, pm_dec=10*u.mas/u.yr) - assert 'pm_ra_cosdec' in str(e.value) + SkyCoord( + ra=150 * u.deg, + dec=-11 * u.deg, + pm_ra=100 * u.mas / u.yr, + pm_dec=10 * u.mas / u.yr, + ) + assert "pm_ra_cosdec" in str(e.value) with pytest.raises(ValueError) as e: - SkyCoord(l=150*u.deg, b=-11*u.deg, - pm_l=100*u.mas/u.yr, pm_b=10*u.mas/u.yr, - frame='galactic') - assert 'pm_l_cosb' in str(e.value) + SkyCoord( + l=150 * u.deg, + b=-11 * u.deg, + pm_l=100 * u.mas / u.yr, + pm_b=10 * u.mas / u.yr, + frame="galactic", + ) + assert "pm_l_cosb" in str(e.value) # The special error should not turn on here: with pytest.raises(ValueError) as e: - SkyCoord(x=1*u.pc, y=2*u.pc, z=3*u.pc, - pm_ra=100*u.mas/u.yr, pm_dec=10*u.mas/u.yr, - representation_type='cartesian') - assert 'pm_ra_cosdec' not in str(e.value) + SkyCoord( + x=1 * u.pc, + y=2 * u.pc, + z=3 * u.pc, + pm_ra=100 * u.mas / u.yr, + pm_dec=10 * u.mas / u.yr, + representation_type="cartesian", + ) + assert "pm_ra_cosdec" not in str(e.value) def test_contained_by(): @@ -1801,25 +2045,17 @@ def test_contained_by(): NAXIS2 = 2078 / length of second array dimension """ - header = fits.Header.fromstring(header.strip(),'\n') - test_wcs = WCS(header) - - coord = SkyCoord(254,2,unit='deg') - assert coord.contained_by(test_wcs) == True - - coord = SkyCoord(240,2,unit='deg') - assert coord.contained_by(test_wcs) == False + test_wcs = WCS(fits.Header.fromstring(header.strip(), "\n")) + assert SkyCoord(254, 2, unit="deg").contained_by(test_wcs) + assert not SkyCoord(240, 2, unit="deg").contained_by(test_wcs) - img = np.zeros((2136,2078)) - coord = SkyCoord(250,2,unit='deg') - assert coord.contained_by(test_wcs, img) == True - - coord = SkyCoord(240,2,unit='deg') - assert coord.contained_by(test_wcs, img) == False + img = np.zeros((2136, 2078)) + assert SkyCoord(250, 2, unit="deg").contained_by(test_wcs, img) + assert not SkyCoord(240, 2, unit="deg").contained_by(test_wcs, img) ra = np.array([254.2, 254.1]) dec = np.array([2, 12.1]) - coords = SkyCoord(ra, dec, unit='deg') + coords = SkyCoord(ra, dec, unit="deg") assert np.all(test_wcs.footprint_contains(coords) == np.array([True, False])) @@ -1833,60 +2069,77 @@ class MockHeliographicStonyhurst(BaseCoordinateFrame): default_representation = SphericalRepresentation frame_specific_representation_info = { - SphericalRepresentation: [RepresentationMapping(reprname='lon', - framename='lon', - defaultunit=u.deg), - RepresentationMapping(reprname='lat', - framename='lat', - defaultunit=u.deg), - RepresentationMapping(reprname='distance', - framename='radius', - defaultunit=None)] + SphericalRepresentation: [ + RepresentationMapping( + reprname="lon", framename="lon", defaultunit=u.deg + ), + RepresentationMapping( + reprname="lat", framename="lat", defaultunit=u.deg + ), + RepresentationMapping( + reprname="distance", framename="radius", defaultunit=None + ), + ] } - fr = MockHeliographicStonyhurst(lon=1*u.deg, lat=2*u.deg, radius=10*u.au) - SkyCoord(0*u.deg, fr.lat, fr.radius, frame=fr) # this was the failure + fr = MockHeliographicStonyhurst(lon=1 * u.deg, lat=2 * u.deg, radius=10 * u.au) + SkyCoord(0 * u.deg, fr.lat, fr.radius, frame=fr) # this was the failure def test_multiple_aliases(): # Define a frame with multiple aliases class MultipleAliasesFrame(BaseCoordinateFrame): - name = ['alias_1', 'alias_2'] + name = ["alias_1", "alias_2"] default_representation = SphericalRepresentation # Register a transform, which adds the aliases to the transform graph tfun = lambda c, f: f.__class__(lon=c.lon, lat=c.lat) - ftrans = FunctionTransform(tfun, MultipleAliasesFrame, MultipleAliasesFrame, - register_graph=frame_transform_graph) + ftrans = FunctionTransform( + tfun, + MultipleAliasesFrame, + MultipleAliasesFrame, + register_graph=frame_transform_graph, + ) - coord = SkyCoord(lon=1*u.deg, lat=2*u.deg, frame=MultipleAliasesFrame) + coord = SkyCoord(lon=1 * u.deg, lat=2 * u.deg, frame=MultipleAliasesFrame) # Test attribute-style access returns self (not a copy) assert coord.alias_1 is coord assert coord.alias_2 is coord # Test for aliases in __dir__() - assert 'alias_1' in coord.__dir__() - assert 'alias_2' in coord.__dir__() + assert "alias_1" in coord.__dir__() + assert "alias_2" in coord.__dir__() # Test transform_to() calls - assert isinstance(coord.transform_to('alias_1').frame, MultipleAliasesFrame) - assert isinstance(coord.transform_to('alias_2').frame, MultipleAliasesFrame) + assert isinstance(coord.transform_to("alias_1").frame, MultipleAliasesFrame) + assert isinstance(coord.transform_to("alias_2").frame, MultipleAliasesFrame) ftrans.unregister(frame_transform_graph) -@pytest.mark.parametrize("kwargs, error_message", [ - ( - {"ra": 1, "dec": 1, "distance": 1 * u.pc, "unit": "deg"}, - r"Unit 'deg' \(angle\) could not be applied to 'distance'. ", - ), - ( - {"rho": 1 * u.m, "phi": 1, "z": 1 * u.m, "unit": "deg", "representation_type": "cylindrical"}, - r"Unit 'deg' \(angle\) could not be applied to 'rho'. ", - ), -]) -def test_passing_inconsistent_coordinates_and_units_raises_helpful_error(kwargs, error_message): +@pytest.mark.parametrize( + "kwargs, error_message", + [ + ( + {"ra": 1, "dec": 1, "distance": 1 * u.pc, "unit": "deg"}, + r"Unit 'deg' \(angle\) could not be applied to 'distance'. ", + ), + ( + { + "rho": 1 * u.m, + "phi": 1, + "z": 1 * u.m, + "unit": "deg", + "representation_type": "cylindrical", + }, + r"Unit 'deg' \(angle\) could not be applied to 'rho'. ", + ), + ], +) +def test_passing_inconsistent_coordinates_and_units_raises_helpful_error( + kwargs, error_message +): # https://github.com/astropy/astropy/issues/10725 with pytest.raises(ValueError, match=error_message): SkyCoord(**kwargs) @@ -1895,16 +2148,20 @@ def test_passing_inconsistent_coordinates_and_units_raises_helpful_error(kwargs, @pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy.") def test_match_to_catalog_3d_and_sky(): # Test for issue #5857. See PR #11449 - cfk5_default = SkyCoord([1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree, distance=[1, 1, 1.5, 1] * u.kpc, - frame='fk5') - cfk5_J1950 = cfk5_default.transform_to(FK5(equinox='J1950')) + cfk5_default = SkyCoord( + [1, 2, 3, 4] * u.degree, + [0, 0, 0, 0] * u.degree, + distance=[1, 1, 1.5, 1] * u.kpc, + frame="fk5", + ) + cfk5_J1950 = cfk5_default.transform_to(FK5(equinox="J1950")) idx, angle, quantity = cfk5_J1950.match_to_catalog_3d(cfk5_default) npt.assert_array_equal(idx, [0, 1, 2, 3]) - assert_allclose(angle, 0*u.deg, atol=1e-14*u.deg, rtol=0) - assert_allclose(quantity, 0*u.kpc, atol=1e-14*u.kpc, rtol=0) + assert_allclose(angle, 0 * u.deg, atol=1e-14 * u.deg, rtol=0) + assert_allclose(quantity, 0 * u.kpc, atol=1e-14 * u.kpc, rtol=0) idx, angle, distance = cfk5_J1950.match_to_catalog_sky(cfk5_default) npt.assert_array_equal(idx, [0, 1, 2, 3]) - assert_allclose(angle, 0 * u.deg, atol=1e-14*u.deg, rtol=0) - assert_allclose(distance, 0*u.kpc, atol=1e-14*u.kpc, rtol=0) + assert_allclose(angle, 0 * u.deg, atol=1e-14 * u.deg, rtol=0) + assert_allclose(distance, 0 * u.kpc, atol=1e-14 * u.kpc, rtol=0) diff --git a/astropy/coordinates/tests/test_sky_coord_velocities.py b/astropy/coordinates/tests/test_sky_coord_velocities.py index 896a7a119cd..b8c02171fe1 100644 --- a/astropy/coordinates/tests/test_sky_coord_velocities.py +++ b/astropy/coordinates/tests/test_sky_coord_velocities.py @@ -31,73 +31,97 @@ def test_creation_frameobjs(): - i = ICRS(1*u.deg, 2*u.deg, pm_ra_cosdec=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr) + i = ICRS( + 1 * u.deg, 2 * u.deg, pm_ra_cosdec=0.2 * u.mas / u.yr, pm_dec=0.1 * u.mas / u.yr + ) sc = SkyCoord(i) - for attrnm in ['ra', 'dec', 'pm_ra_cosdec', 'pm_dec']: + for attrnm in ["ra", "dec", "pm_ra_cosdec", "pm_dec"]: assert_quantity_allclose(getattr(i, attrnm), getattr(sc, attrnm)) - sc_nod = SkyCoord(ICRS(1*u.deg, 2*u.deg)) + sc_nod = SkyCoord(ICRS(1 * u.deg, 2 * u.deg)) - for attrnm in ['ra', 'dec']: + for attrnm in ["ra", "dec"]: assert_quantity_allclose(getattr(sc, attrnm), getattr(sc_nod, attrnm)) def test_creation_attrs(): - sc1 = SkyCoord(1*u.deg, 2*u.deg, - pm_ra_cosdec=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr, - frame='fk5') - assert_quantity_allclose(sc1.ra, 1*u.deg) - assert_quantity_allclose(sc1.dec, 2*u.deg) - assert_quantity_allclose(sc1.pm_ra_cosdec, .2*u.arcsec/u.kyr) - assert_quantity_allclose(sc1.pm_dec, .1*u.arcsec/u.kyr) - - sc2 = SkyCoord(1*u.deg, 2*u.deg, - pm_ra=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr, - differential_type=SphericalDifferential) - assert_quantity_allclose(sc2.ra, 1*u.deg) - assert_quantity_allclose(sc2.dec, 2*u.deg) - assert_quantity_allclose(sc2.pm_ra, .2*u.arcsec/u.kyr) - assert_quantity_allclose(sc2.pm_dec, .1*u.arcsec/u.kyr) - - sc3 = SkyCoord('1:2:3 4:5:6', - pm_ra_cosdec=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr, - unit=(u.hour, u.deg)) - - assert_quantity_allclose(sc3.ra, 1*u.hourangle + 2*u.arcmin*15 + 3*u.arcsec*15) - assert_quantity_allclose(sc3.dec, 4*u.deg + 5*u.arcmin + 6*u.arcsec) + sc1 = SkyCoord( + 1 * u.deg, + 2 * u.deg, + pm_ra_cosdec=0.2 * u.mas / u.yr, + pm_dec=0.1 * u.mas / u.yr, + frame="fk5", + ) + assert_quantity_allclose(sc1.ra, 1 * u.deg) + assert_quantity_allclose(sc1.dec, 2 * u.deg) + assert_quantity_allclose(sc1.pm_ra_cosdec, 0.2 * u.arcsec / u.kyr) + assert_quantity_allclose(sc1.pm_dec, 0.1 * u.arcsec / u.kyr) + + sc2 = SkyCoord( + 1 * u.deg, + 2 * u.deg, + pm_ra=0.2 * u.mas / u.yr, + pm_dec=0.1 * u.mas / u.yr, + differential_type=SphericalDifferential, + ) + assert_quantity_allclose(sc2.ra, 1 * u.deg) + assert_quantity_allclose(sc2.dec, 2 * u.deg) + assert_quantity_allclose(sc2.pm_ra, 0.2 * u.arcsec / u.kyr) + assert_quantity_allclose(sc2.pm_dec, 0.1 * u.arcsec / u.kyr) + + sc3 = SkyCoord( + "1:2:3 4:5:6", + pm_ra_cosdec=0.2 * u.mas / u.yr, + pm_dec=0.1 * u.mas / u.yr, + unit=(u.hour, u.deg), + ) + + assert_quantity_allclose( + sc3.ra, 1 * u.hourangle + 2 * u.arcmin * 15 + 3 * u.arcsec * 15 + ) + assert_quantity_allclose(sc3.dec, 4 * u.deg + 5 * u.arcmin + 6 * u.arcsec) # might as well check with sillier units? - assert_quantity_allclose(sc3.pm_ra_cosdec, 1.2776637006616473e-07 * u.arcmin / u.fortnight) + assert_quantity_allclose( + sc3.pm_ra_cosdec, 1.2776637006616473e-07 * u.arcmin / u.fortnight + ) assert_quantity_allclose(sc3.pm_dec, 6.388318503308237e-08 * u.arcmin / u.fortnight) def test_creation_copy_basic(): - i = ICRS(1*u.deg, 2*u.deg, pm_ra_cosdec=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr) + i = ICRS( + 1 * u.deg, 2 * u.deg, pm_ra_cosdec=0.2 * u.mas / u.yr, pm_dec=0.1 * u.mas / u.yr + ) sc = SkyCoord(i) sc_cpy = SkyCoord(sc) - for attrnm in ['ra', 'dec', 'pm_ra_cosdec', 'pm_dec']: + for attrnm in ["ra", "dec", "pm_ra_cosdec", "pm_dec"]: assert_quantity_allclose(getattr(sc, attrnm), getattr(sc_cpy, attrnm)) def test_creation_copy_rediff(): - sc = SkyCoord(1*u.deg, 2*u.deg, - pm_ra=.2*u.mas/u.yr, pm_dec=.1*u.mas/u.yr, - differential_type=SphericalDifferential) + sc = SkyCoord( + 1 * u.deg, + 2 * u.deg, + pm_ra=0.2 * u.mas / u.yr, + pm_dec=0.1 * u.mas / u.yr, + differential_type=SphericalDifferential, + ) sc_cpy = SkyCoord(sc) - for attrnm in ['ra', 'dec', 'pm_ra', 'pm_dec']: + for attrnm in ["ra", "dec", "pm_ra", "pm_dec"]: assert_quantity_allclose(getattr(sc, attrnm), getattr(sc_cpy, attrnm)) sc_newdiff = SkyCoord(sc, differential_type=SphericalCosLatDifferential) reprepr = sc.represent_as(SphericalRepresentation, SphericalCosLatDifferential) - assert_quantity_allclose(sc_newdiff.pm_ra_cosdec, - reprepr.differentials['s'].d_lon_coslat) + assert_quantity_allclose( + sc_newdiff.pm_ra_cosdec, reprepr.differentials["s"].d_lon_coslat + ) def test_creation_cartesian(): - rep = CartesianRepresentation([10, 0., 0.]*u.pc) - dif = CartesianDifferential([0, 100, 0.]*u.pc/u.Myr) + rep = CartesianRepresentation([10, 0.0, 0.0] * u.pc) + dif = CartesianDifferential([0, 100, 0.0] * u.pc / u.Myr) rep = rep.with_differentials(dif) c = SkyCoord(rep) @@ -106,7 +130,7 @@ def test_creation_cartesian(): def test_useful_error_missing(): - sc_nod = SkyCoord(ICRS(1*u.deg, 2*u.deg)) + sc_nod = SkyCoord(ICRS(1 * u.deg, 2 * u.deg)) try: sc_nod.l except AttributeError as e: @@ -125,34 +149,39 @@ def test_useful_error_missing(): # ----------------------Operations on SkyCoords w/ velocities------------------- # define some fixtures to get baseline coordinates to try operations with -@pytest.fixture(scope="module", params=[(False, False), - (True, False), - (False, True), - (True, True)]) +@pytest.fixture( + scope="module", params=[(False, False), (True, False), (False, True), (True, True)] +) def sc(request): incldist, inclrv = request.param - args = [1*u.deg, 2*u.deg] - kwargs = dict(pm_dec=1*u.mas/u.yr, pm_ra_cosdec=2*u.mas/u.yr) + args = [1 * u.deg, 2 * u.deg] + kwargs = dict(pm_dec=1 * u.mas / u.yr, pm_ra_cosdec=2 * u.mas / u.yr) if incldist: - kwargs['distance'] = 213.4*u.pc + kwargs["distance"] = 213.4 * u.pc if inclrv: - kwargs['radial_velocity'] = 61*u.km/u.s + kwargs["radial_velocity"] = 61 * u.km / u.s return SkyCoord(*args, **kwargs) @pytest.fixture(scope="module") def scmany(): - return SkyCoord(ICRS(ra=[1]*100*u.deg, dec=[2]*100*u.deg, - pm_ra_cosdec=np.random.randn(100)*u.mas/u.yr, - pm_dec=np.random.randn(100)*u.mas/u.yr,)) + return SkyCoord( + ICRS( + ra=[1] * 100 * u.deg, + dec=[2] * 100 * u.deg, + pm_ra_cosdec=np.random.randn(100) * u.mas / u.yr, + pm_dec=np.random.randn(100) * u.mas / u.yr, + ) + ) @pytest.fixture(scope="module") def sc_for_sep(): - return SkyCoord(1*u.deg, 2*u.deg, - pm_dec=1*u.mas/u.yr, pm_ra_cosdec=2*u.mas/u.yr) + return SkyCoord( + 1 * u.deg, 2 * u.deg, pm_dec=1 * u.mas / u.yr, pm_ra_cosdec=2 * u.mas / u.yr + ) def test_separation(sc, sc_for_sep): @@ -160,14 +189,15 @@ def test_separation(sc, sc_for_sep): def test_accessors(sc, scmany): - sc.data.differentials['s'] + sc.data.differentials["s"] sph = sc.spherical gal = sc.galactic - if (sc.data.get_name().startswith('unit') and not - sc.data.differentials['s'].get_name().startswith('unit')): + if sc.data.get_name().startswith("unit") and not sc.data.differentials[ + "s" + ].get_name().startswith("unit"): # this xfail can be eliminated when issue #7028 is resolved - pytest.xfail('.velocity fails if there is an RV but not distance') + pytest.xfail(".velocity fails if there is an RV but not distance") sc.velocity assert isinstance(sph, SphericalRepresentation) @@ -182,7 +212,7 @@ def test_accessors(sc, scmany): def test_transforms(sc): - trans = sc.transform_to('galactic') + trans = sc.transform_to("galactic") assert isinstance(trans.frame, Galactic) @@ -190,13 +220,13 @@ def test_transforms_diff(sc): # note that arguably this *should* fail for the no-distance cases: 3D # information is necessary to truly solve this, hence the xfail if not sc.distance.unit.is_equivalent(u.m): - pytest.xfail('Should fail for no-distance cases') + pytest.xfail("Should fail for no-distance cases") else: - trans = sc.transform_to(PrecessedGeocentric(equinox='B1975')) + trans = sc.transform_to(PrecessedGeocentric(equinox="B1975")) assert isinstance(trans.frame, PrecessedGeocentric) -@pytest.mark.skipif(not HAS_SCIPY, reason='Requires scipy') +@pytest.mark.skipif(not HAS_SCIPY, reason="Requires scipy") def test_matching(sc, scmany): # just check that it works and yields something idx, d2d, d3d = sc.match_to_catalog_sky(scmany) @@ -208,64 +238,96 @@ def test_position_angle(sc, sc_for_sep): def test_constellations(sc): const = sc.get_constellation() - assert const == 'Pisces' + assert const == "Pisces" def test_separation_3d_with_differentials(): - c1 = SkyCoord(ra=138*u.deg, dec=-17*u.deg, distance=100*u.pc, - pm_ra_cosdec=5*u.mas/u.yr, - pm_dec=-7*u.mas/u.yr, - radial_velocity=160*u.km/u.s) - c2 = SkyCoord(ra=138*u.deg, dec=-17*u.deg, distance=105*u.pc, - pm_ra_cosdec=15*u.mas/u.yr, - pm_dec=-74*u.mas/u.yr, - radial_velocity=-60*u.km/u.s) + c1 = SkyCoord( + ra=138 * u.deg, + dec=-17 * u.deg, + distance=100 * u.pc, + pm_ra_cosdec=5 * u.mas / u.yr, + pm_dec=-7 * u.mas / u.yr, + radial_velocity=160 * u.km / u.s, + ) + c2 = SkyCoord( + ra=138 * u.deg, + dec=-17 * u.deg, + distance=105 * u.pc, + pm_ra_cosdec=15 * u.mas / u.yr, + pm_dec=-74 * u.mas / u.yr, + radial_velocity=-60 * u.km / u.s, + ) sep = c1.separation_3d(c2) - assert_quantity_allclose(sep, 5*u.pc) + assert_quantity_allclose(sep, 5 * u.pc) -@pytest.mark.parametrize('sph_type', ['spherical', 'unitspherical']) +@pytest.mark.parametrize("sph_type", ["spherical", "unitspherical"]) def test_cartesian_to_spherical(sph_type): """Conversion to unitspherical should work, even if we lose distance.""" - c = SkyCoord(x=1*u.kpc, y=0*u.kpc, z=0*u.kpc, - v_x=10*u.km/u.s, v_y=0*u.km/u.s, v_z=4.74*u.km/u.s, - representation_type='cartesian') + c = SkyCoord( + x=1 * u.kpc, + y=0 * u.kpc, + z=0 * u.kpc, + v_x=10 * u.km / u.s, + v_y=0 * u.km / u.s, + v_z=4.74 * u.km / u.s, + representation_type="cartesian", + ) c.representation_type = sph_type assert c.ra == 0 assert c.dec == 0 assert c.pm_ra == 0 - assert u.allclose(c.pm_dec, 1*u.mas/u.yr, rtol=1e-3) - assert c.radial_velocity == 10*u.km/u.s - if sph_type == 'spherical': - assert c.distance == 1*u.kpc + assert u.allclose(c.pm_dec, 1 * u.mas / u.yr, rtol=1e-3) + assert c.radial_velocity == 10 * u.km / u.s + if sph_type == "spherical": + assert c.distance == 1 * u.kpc else: - assert not hasattr(c, 'distance') - - -@pytest.mark.parametrize('diff_info, diff_cls', [ - (dict(radial_velocity=[20, 30]*u.km/u.s), RadialDifferential), - (dict(pm_ra=[2, 3]*u.mas/u.yr, pm_dec=[-3, -4]*u.mas/u.yr, - differential_type='unitspherical'), UnitSphericalDifferential), - (dict(pm_ra_cosdec=[2, 3]*u.mas/u.yr, pm_dec=[-3, -4]*u.mas/u.yr), - UnitSphericalCosLatDifferential)], scope='class') + assert not hasattr(c, "distance") + + +@pytest.mark.parametrize( + "diff_info, diff_cls", + [ + (dict(radial_velocity=[20, 30] * u.km / u.s), RadialDifferential), + ( + dict( + pm_ra=[2, 3] * u.mas / u.yr, + pm_dec=[-3, -4] * u.mas / u.yr, + differential_type="unitspherical", + ), + UnitSphericalDifferential, + ), + ( + dict(pm_ra_cosdec=[2, 3] * u.mas / u.yr, pm_dec=[-3, -4] * u.mas / u.yr), + UnitSphericalCosLatDifferential, + ), + ], + scope="class", +) class TestDifferentialClassPropagation: """Test that going in between spherical and unit-spherical, we do not change differential type (since both can handle the same types). """ + def test_sc_unit_spherical_with_pm_or_rv_only(self, diff_info, diff_cls): - sc = SkyCoord(ra=[10, 20]*u.deg, dec=[-10, 10]*u.deg, **diff_info) + sc = SkyCoord(ra=[10, 20] * u.deg, dec=[-10, 10] * u.deg, **diff_info) assert isinstance(sc.data, UnitSphericalRepresentation) - assert isinstance(sc.data.differentials['s'], diff_cls) - sr = sc.represent_as('spherical') + assert isinstance(sc.data.differentials["s"], diff_cls) + sr = sc.represent_as("spherical") assert isinstance(sr, SphericalRepresentation) - assert isinstance(sr.differentials['s'], diff_cls) + assert isinstance(sr.differentials["s"], diff_cls) def test_sc_spherical_with_pm_or_rv_only(self, diff_info, diff_cls): - sc = SkyCoord(ra=[10, 20]*u.deg, dec=[-10, 10]*u.deg, - distance=1.*u.kpc, **diff_info) + sc = SkyCoord( + ra=[10, 20] * u.deg, + dec=[-10, 10] * u.deg, + distance=1.0 * u.kpc, + **diff_info + ) assert isinstance(sc.data, SphericalRepresentation) - assert isinstance(sc.data.differentials['s'], diff_cls) - sr = sc.represent_as('unitspherical') + assert isinstance(sc.data.differentials["s"], diff_cls) + sr = sc.represent_as("unitspherical") assert isinstance(sr, UnitSphericalRepresentation) - assert isinstance(sr.differentials['s'], diff_cls) + assert isinstance(sr.differentials["s"], diff_cls) diff --git a/astropy/coordinates/tests/test_skyoffset_transformations.py b/astropy/coordinates/tests/test_skyoffset_transformations.py index 3d551ead1ab..20352b7a6ce 100644 --- a/astropy/coordinates/tests/test_skyoffset_transformations.py +++ b/astropy/coordinates/tests/test_skyoffset_transformations.py @@ -19,50 +19,60 @@ def test_altaz_attribute_transforms(): """Test transforms between AltAz frames with different attributes.""" - el1 = EarthLocation(0*u.deg, 0*u.deg, 0*u.m) - origin1 = AltAz(0 * u.deg, 0*u.deg, obstime=Time("2000-01-01T12:00:00"), - location=el1) + el1 = EarthLocation(0 * u.deg, 0 * u.deg, 0 * u.m) + origin1 = AltAz( + 0 * u.deg, 0 * u.deg, obstime=Time("2000-01-01T12:00:00"), location=el1 + ) frame1 = SkyOffsetFrame(origin=origin1) coo1 = SkyCoord(1 * u.deg, 1 * u.deg, frame=frame1) - el2 = EarthLocation(0*u.deg, 0*u.deg, 0*u.m) - origin2 = AltAz(0 * u.deg, 0*u.deg, obstime=Time("2000-01-01T11:00:00"), - location=el2) + el2 = EarthLocation(0 * u.deg, 0 * u.deg, 0 * u.m) + origin2 = AltAz( + 0 * u.deg, 0 * u.deg, obstime=Time("2000-01-01T11:00:00"), location=el2 + ) frame2 = SkyOffsetFrame(origin=origin2) coo2 = coo1.transform_to(frame2) coo2_expected = [1.22522446, 0.70624298] * u.deg - assert_allclose([coo2.lon.wrap_at(180*u.deg), coo2.lat], - coo2_expected, atol=convert_precision) - - el3 = EarthLocation(0*u.deg, 90*u.deg, 0*u.m) - origin3 = AltAz(0 * u.deg, 90*u.deg, obstime=Time("2000-01-01T12:00:00"), - location=el3) + assert_allclose( + [coo2.lon.wrap_at(180 * u.deg), coo2.lat], coo2_expected, atol=convert_precision + ) + + el3 = EarthLocation(0 * u.deg, 90 * u.deg, 0 * u.m) + origin3 = AltAz( + 0 * u.deg, 90 * u.deg, obstime=Time("2000-01-01T12:00:00"), location=el3 + ) frame3 = SkyOffsetFrame(origin=origin3) coo3 = coo2.transform_to(frame3) - assert_allclose([coo3.lon.wrap_at(180*u.deg), coo3.lat], - [1*u.deg, 1*u.deg], atol=convert_precision) - - -@pytest.mark.parametrize("inradec,expectedlatlon, tolsep", [ - ((45, 45)*u.deg, (0, 0)*u.deg, .001*u.arcsec), - ((45, 0)*u.deg, (0, -45)*u.deg, .001*u.arcsec), - ((45, 90)*u.deg, (0, 45)*u.deg, .001*u.arcsec), - ((46, 45)*u.deg, (1*np.cos(45*u.deg), 0)*u.deg, 16*u.arcsec), - ]) -def test_skyoffset(inradec, expectedlatlon, tolsep, originradec=(45, 45)*u.deg): + assert_allclose( + [coo3.lon.wrap_at(180 * u.deg), coo3.lat], + [1 * u.deg, 1 * u.deg], + atol=convert_precision, + ) + + +@pytest.mark.parametrize( + "inradec,expectedlatlon, tolsep", + [ + ((45, 45) * u.deg, (0, 0) * u.deg, 0.001 * u.arcsec), + ((45, 0) * u.deg, (0, -45) * u.deg, 0.001 * u.arcsec), + ((45, 90) * u.deg, (0, 45) * u.deg, 0.001 * u.arcsec), + ((46, 45) * u.deg, (1 * np.cos(45 * u.deg), 0) * u.deg, 16 * u.arcsec), + ], +) +def test_skyoffset(inradec, expectedlatlon, tolsep, originradec=(45, 45) * u.deg): origin = ICRS(*originradec) skyoffset_frame = SkyOffsetFrame(origin=origin) skycoord = SkyCoord(*inradec, frame=ICRS) skycoord_inaf = skycoord.transform_to(skyoffset_frame) - assert hasattr(skycoord_inaf, 'lon') - assert hasattr(skycoord_inaf, 'lat') + assert hasattr(skycoord_inaf, "lon") + assert hasattr(skycoord_inaf, "lat") expected = SkyCoord(*expectedlatlon, frame=skyoffset_frame) assert skycoord_inaf.separation(expected) < tolsep # Check we can also transform back (regression test for gh-11254). roundtrip = skycoord_inaf.transform_to(ICRS()) - assert roundtrip.separation(skycoord) < 1*u.uas + assert roundtrip.separation(skycoord) < 1 * u.uas def test_skyoffset_functional_ra(): @@ -72,19 +82,19 @@ def test_skyoffset_functional_ra(): # staying away from the edges input_ra = np.linspace(0, 360, 12)[1:-1] input_dec = np.linspace(-90, 90, 12)[1:-1] - icrs_coord = ICRS(ra=input_ra*u.deg, - dec=input_dec*u.deg, - distance=1.*u.kpc) + icrs_coord = ICRS(ra=input_ra * u.deg, dec=input_dec * u.deg, distance=1.0 * u.kpc) for ra in np.linspace(0, 360, 24): # expected rotation - expected = ICRS(ra=np.linspace(0-ra, 360-ra, 12)[1:-1]*u.deg, - dec=np.linspace(-90, 90, 12)[1:-1]*u.deg, - distance=1.*u.kpc) + expected = ICRS( + ra=np.linspace(0 - ra, 360 - ra, 12)[1:-1] * u.deg, + dec=np.linspace(-90, 90, 12)[1:-1] * u.deg, + distance=1.0 * u.kpc, + ) expected_xyz = expected.cartesian.xyz # actual transformation to the frame - skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra*u.deg, 0*u.deg)) + skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra * u.deg, 0 * u.deg)) actual = icrs_coord.transform_to(skyoffset_frame) actual_xyz = actual.cartesian.xyz @@ -93,10 +103,10 @@ def test_skyoffset_functional_ra(): roundtrip_xyz = roundtrip.cartesian.xyz # Verify - assert_allclose(actual_xyz, expected_xyz, atol=1E-5*u.kpc) - assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1E-5*u.deg) - assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1E-5*u.deg) - assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1E-5*u.kpc) + assert_allclose(actual_xyz, expected_xyz, atol=1e-5 * u.kpc) + assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1e-5 * u.deg) + assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1e-5 * u.deg) + assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1e-5 * u.kpc) def test_skyoffset_functional_dec(): @@ -108,27 +118,31 @@ def test_skyoffset_functional_dec(): input_dec = np.linspace(-90, 90, 12)[1:-1] input_ra_rad = np.deg2rad(input_ra) input_dec_rad = np.deg2rad(input_dec) - icrs_coord = ICRS(ra=input_ra*u.deg, - dec=input_dec*u.deg, - distance=1.*u.kpc) + icrs_coord = ICRS(ra=input_ra * u.deg, dec=input_dec * u.deg, distance=1.0 * u.kpc) # Dec rotations # Done in xyz space because dec must be [-90,90] for dec in np.linspace(-90, 90, 13): # expected rotation dec_rad = -np.deg2rad(dec) + # fmt: off expected_x = (-np.sin(input_dec_rad) * np.sin(dec_rad) + np.cos(input_ra_rad) * np.cos(input_dec_rad) * np.cos(dec_rad)) expected_y = (np.sin(input_ra_rad) * np.cos(input_dec_rad)) expected_z = (np.sin(input_dec_rad) * np.cos(dec_rad) + np.sin(dec_rad) * np.cos(input_ra_rad) * np.cos(input_dec_rad)) - expected = SkyCoord(x=expected_x, - y=expected_y, - z=expected_z, unit='kpc', representation_type='cartesian') + # fmt: on + expected = SkyCoord( + x=expected_x, + y=expected_y, + z=expected_z, + unit="kpc", + representation_type="cartesian", + ) expected_xyz = expected.cartesian.xyz # actual transformation to the frame - skyoffset_frame = SkyOffsetFrame(origin=ICRS(0*u.deg, dec*u.deg)) + skyoffset_frame = SkyOffsetFrame(origin=ICRS(0 * u.deg, dec * u.deg)) actual = icrs_coord.transform_to(skyoffset_frame) actual_xyz = actual.cartesian.xyz @@ -136,10 +150,10 @@ def test_skyoffset_functional_dec(): roundtrip = actual.transform_to(ICRS()) # Verify - assert_allclose(actual_xyz, expected_xyz, atol=1E-5*u.kpc) - assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1E-5*u.deg) - assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1E-5*u.deg) - assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1E-5*u.kpc) + assert_allclose(actual_xyz, expected_xyz, atol=1e-5 * u.kpc) + assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1e-5 * u.deg) + assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1e-5 * u.deg) + assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1e-5 * u.kpc) def test_skyoffset_functional_ra_dec(): @@ -151,15 +165,14 @@ def test_skyoffset_functional_ra_dec(): input_dec = np.linspace(-90, 90, 12)[1:-1] input_ra_rad = np.deg2rad(input_ra) input_dec_rad = np.deg2rad(input_dec) - icrs_coord = ICRS(ra=input_ra*u.deg, - dec=input_dec*u.deg, - distance=1.*u.kpc) + icrs_coord = ICRS(ra=input_ra * u.deg, dec=input_dec * u.deg, distance=1.0 * u.kpc) for ra in np.linspace(0, 360, 10): for dec in np.linspace(-90, 90, 5): # expected rotation dec_rad = -np.deg2rad(dec) ra_rad = np.deg2rad(ra) + # fmt: off expected_x = (-np.sin(input_dec_rad) * np.sin(dec_rad) + np.cos(input_ra_rad) * np.cos(input_dec_rad) * np.cos(dec_rad) * np.cos(ra_rad) + np.sin(input_ra_rad) * np.cos(input_dec_rad) * np.cos(dec_rad) * np.sin(ra_rad)) @@ -168,13 +181,18 @@ def test_skyoffset_functional_ra_dec(): expected_z = (np.sin(input_dec_rad) * np.cos(dec_rad) + np.sin(dec_rad) * np.cos(ra_rad) * np.cos(input_ra_rad) * np.cos(input_dec_rad) + np.sin(dec_rad) * np.sin(ra_rad) * np.sin(input_ra_rad) * np.cos(input_dec_rad)) - expected = SkyCoord(x=expected_x, - y=expected_y, - z=expected_z, unit='kpc', representation_type='cartesian') + # fmp: on + expected = SkyCoord( + x=expected_x, + y=expected_y, + z=expected_z, + unit='kpc', + representation_type='cartesian', + ) expected_xyz = expected.cartesian.xyz # actual transformation to the frame - skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra*u.deg, dec*u.deg)) + skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra * u.deg, dec * u.deg)) actual = icrs_coord.transform_to(skyoffset_frame) actual_xyz = actual.cartesian.xyz @@ -182,31 +200,39 @@ def test_skyoffset_functional_ra_dec(): roundtrip = actual.transform_to(ICRS()) # Verify - assert_allclose(actual_xyz, expected_xyz, atol=1E-5*u.kpc) - assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1E-4*u.deg) - assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1E-5*u.deg) - assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1E-5*u.kpc) + assert_allclose(actual_xyz, expected_xyz, atol=1e-5 * u.kpc) + assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1e-4 * u.deg) + assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1e-5 * u.deg) + assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1e-5 * u.kpc) def test_skycoord_skyoffset_frame(): - m31 = SkyCoord(10.6847083, 41.26875, frame='icrs', unit=u.deg) - m33 = SkyCoord(23.4621, 30.6599417, frame='icrs', unit=u.deg) + m31 = SkyCoord(10.6847083, 41.26875, frame="icrs", unit=u.deg) + m33 = SkyCoord(23.4621, 30.6599417, frame="icrs", unit=u.deg) m31_astro = m31.skyoffset_frame() m31_in_m31 = m31.transform_to(m31_astro) m33_in_m31 = m33.transform_to(m31_astro) - assert_allclose([m31_in_m31.lon, m31_in_m31.lat], [0, 0]*u.deg, atol=1e-10*u.deg) - assert_allclose([m33_in_m31.lon, m33_in_m31.lat], [11.13135175, -9.79084759]*u.deg) + assert_allclose( + [m31_in_m31.lon, m31_in_m31.lat], [0, 0] * u.deg, atol=1e-10 * u.deg + ) + assert_allclose( + [m33_in_m31.lon, m33_in_m31.lat], [11.13135175, -9.79084759] * u.deg + ) - assert_allclose(m33.separation(m31), - np.hypot(m33_in_m31.lon, m33_in_m31.lat), - atol=.1*u.deg) + assert_allclose( + m33.separation(m31), np.hypot(m33_in_m31.lon, m33_in_m31.lat), atol=0.1 * u.deg + ) # used below in the next parametrized test m31_sys = [ICRS, FK5, Galactic] -m31_coo = [(10.6847929, 41.2690650), (10.6847929, 41.2690650), (121.1744050, -21.5729360)] +m31_coo = [ + (10.6847929, 41.2690650), + (10.6847929, 41.2690650), + (121.1744050, -21.5729360), +] m31_dist = Distance(770, u.kpc) convert_precision = 1 * u.arcsec roundtrip_precision = 1e-4 * u.degree @@ -219,71 +245,83 @@ def test_skycoord_skyoffset_frame(): m31_params.append((m31_sys[i], m31_sys[j], m31_coo[i], m31_coo[j])) -@pytest.mark.parametrize(('fromsys', 'tosys', 'fromcoo', 'tocoo'), m31_params) +@pytest.mark.parametrize(("fromsys", "tosys", "fromcoo", "tocoo"), m31_params) def test_m31_coord_transforms(fromsys, tosys, fromcoo, tocoo): """ This tests a variety of coordinate conversions for the Chandra point-source catalog location of M31 from NED, via SkyOffsetFrames """ - from_origin = fromsys(fromcoo[0]*u.deg, fromcoo[1]*u.deg, - distance=m31_dist) - from_pos = SkyOffsetFrame(1*u.deg, 1*u.deg, origin=from_origin) - to_origin = tosys(tocoo[0]*u.deg, tocoo[1]*u.deg, distance=m31_dist) + from_origin = fromsys(fromcoo[0] * u.deg, fromcoo[1] * u.deg, distance=m31_dist) + from_pos = SkyOffsetFrame(1 * u.deg, 1 * u.deg, origin=from_origin) + to_origin = tosys(tocoo[0] * u.deg, tocoo[1] * u.deg, distance=m31_dist) to_astroframe = SkyOffsetFrame(origin=to_origin) target_pos = from_pos.transform_to(to_astroframe) - assert_allclose(to_origin.separation(target_pos), - np.hypot(from_pos.lon, from_pos.lat), - atol=convert_precision) + assert_allclose( + to_origin.separation(target_pos), + np.hypot(from_pos.lon, from_pos.lat), + atol=convert_precision, + ) roundtrip_pos = target_pos.transform_to(from_pos) - assert_allclose([roundtrip_pos.lon.wrap_at(180*u.deg), roundtrip_pos.lat], - [1.0*u.deg, 1.0*u.deg], atol=convert_precision) - - -@pytest.mark.parametrize("rotation, expectedlatlon", [ - (0*u.deg, [0, 1]*u.deg), - (180*u.deg, [0, -1]*u.deg), - (90*u.deg, [-1, 0]*u.deg), - (-90*u.deg, [1, 0]*u.deg) - ]) + assert_allclose( + [roundtrip_pos.lon.wrap_at(180 * u.deg), roundtrip_pos.lat], + [1.0 * u.deg, 1.0 * u.deg], + atol=convert_precision, + ) + + +@pytest.mark.parametrize( + "rotation, expectedlatlon", + [ + (0 * u.deg, [0, 1] * u.deg), + (180 * u.deg, [0, -1] * u.deg), + (90 * u.deg, [-1, 0] * u.deg), + (-90 * u.deg, [1, 0] * u.deg), + ], +) def test_rotation(rotation, expectedlatlon): - origin = ICRS(45*u.deg, 45*u.deg) - target = ICRS(45*u.deg, 46*u.deg) + origin = ICRS(45 * u.deg, 45 * u.deg) + target = ICRS(45 * u.deg, 46 * u.deg) aframe = SkyOffsetFrame(origin=origin, rotation=rotation) trans = target.transform_to(aframe) - assert_allclose([trans.lon.wrap_at(180*u.deg), trans.lat], - expectedlatlon, atol=1e-10*u.deg) + assert_allclose( + [trans.lon.wrap_at(180 * u.deg), trans.lat], expectedlatlon, atol=1e-10 * u.deg + ) -@pytest.mark.parametrize("rotation, expectedlatlon", [ - (0*u.deg, [0, 1]*u.deg), - (180*u.deg, [0, -1]*u.deg), - (90*u.deg, [-1, 0]*u.deg), - (-90*u.deg, [1, 0]*u.deg) - ]) +@pytest.mark.parametrize( + "rotation, expectedlatlon", + [ + (0 * u.deg, [0, 1] * u.deg), + (180 * u.deg, [0, -1] * u.deg), + (90 * u.deg, [-1, 0] * u.deg), + (-90 * u.deg, [1, 0] * u.deg), + ], +) def test_skycoord_skyoffset_frame_rotation(rotation, expectedlatlon): """Test if passing a rotation argument via SkyCoord works""" - origin = SkyCoord(45*u.deg, 45*u.deg) - target = SkyCoord(45*u.deg, 46*u.deg) + origin = SkyCoord(45 * u.deg, 45 * u.deg) + target = SkyCoord(45 * u.deg, 46 * u.deg) aframe = origin.skyoffset_frame(rotation=rotation) trans = target.transform_to(aframe) - assert_allclose([trans.lon.wrap_at(180*u.deg), trans.lat], - expectedlatlon, atol=1e-10*u.deg) + assert_allclose( + [trans.lon.wrap_at(180 * u.deg), trans.lat], expectedlatlon, atol=1e-10 * u.deg + ) def test_skyoffset_names(): - origin1 = ICRS(45*u.deg, 45*u.deg) + origin1 = ICRS(45 * u.deg, 45 * u.deg) aframe1 = SkyOffsetFrame(origin=origin1) - assert type(aframe1).__name__ == 'SkyOffsetICRS' + assert type(aframe1).__name__ == "SkyOffsetICRS" - origin2 = Galactic(45*u.deg, 45*u.deg) + origin2 = Galactic(45 * u.deg, 45 * u.deg) aframe2 = SkyOffsetFrame(origin=origin2) - assert type(aframe2).__name__ == 'SkyOffsetGalactic' + assert type(aframe2).__name__ == "SkyOffsetGalactic" def test_skyoffset_origindata(): @@ -293,24 +331,27 @@ def test_skyoffset_origindata(): def test_skyoffset_lonwrap(): - origin = ICRS(45*u.deg, 45*u.deg) - sc = SkyCoord(190*u.deg, -45*u.deg, frame=SkyOffsetFrame(origin=origin)) + origin = ICRS(45 * u.deg, 45 * u.deg) + sc = SkyCoord(190 * u.deg, -45 * u.deg, frame=SkyOffsetFrame(origin=origin)) assert sc.lon < 180 * u.deg - sc2 = SkyCoord(-10*u.deg, -45*u.deg, frame=SkyOffsetFrame(origin=origin)) + sc2 = SkyCoord(-10 * u.deg, -45 * u.deg, frame=SkyOffsetFrame(origin=origin)) assert sc2.lon < 180 * u.deg - sc3 = sc.realize_frame(sc.represent_as('cartesian')) + sc3 = sc.realize_frame(sc.represent_as("cartesian")) assert sc3.lon < 180 * u.deg - sc4 = sc2.realize_frame(sc2.represent_as('cartesian')) + sc4 = sc2.realize_frame(sc2.represent_as("cartesian")) assert sc4.lon < 180 * u.deg def test_skyoffset_velocity(): - c = ICRS(ra=170.9*u.deg, dec=-78.4*u.deg, - pm_ra_cosdec=74.4134*u.mas/u.yr, - pm_dec=-93.2342*u.mas/u.yr) + c = ICRS( + ra=170.9 * u.deg, + dec=-78.4 * u.deg, + pm_ra_cosdec=74.4134 * u.mas / u.yr, + pm_dec=-93.2342 * u.mas / u.yr, + ) skyoffset_frame = SkyOffsetFrame(origin=c) c_skyoffset = c.transform_to(skyoffset_frame) @@ -318,17 +359,23 @@ def test_skyoffset_velocity(): assert_allclose(c_skyoffset.pm_lat, c.pm_dec) -@pytest.mark.parametrize("rotation, expectedpmlonlat", [ - (0*u.deg, [1, 2]*u.mas/u.yr), - (45*u.deg, [-2**-0.5, 3*2**-0.5]*u.mas/u.yr), - (90*u.deg, [-2, 1]*u.mas/u.yr), - (180*u.deg, [-1, -2]*u.mas/u.yr), - (-90*u.deg, [2, -1]*u.mas/u.yr) - ]) +@pytest.mark.parametrize( + "rotation, expectedpmlonlat", + [ + (0 * u.deg, [1, 2] * u.mas / u.yr), + (45 * u.deg, [-(2**-0.5), 3 * 2**-0.5] * u.mas / u.yr), + (90 * u.deg, [-2, 1] * u.mas / u.yr), + (180 * u.deg, [-1, -2] * u.mas / u.yr), + (-90 * u.deg, [2, -1] * u.mas / u.yr), + ], +) def test_skyoffset_velocity_rotation(rotation, expectedpmlonlat): - sc = SkyCoord(ra=170.9*u.deg, dec=-78.4*u.deg, - pm_ra_cosdec=1*u.mas/u.yr, - pm_dec=2*u.mas/u.yr) + sc = SkyCoord( + ra=170.9 * u.deg, + dec=-78.4 * u.deg, + pm_ra_cosdec=1 * u.mas / u.yr, + pm_dec=2 * u.mas / u.yr, + ) c_skyoffset0 = sc.transform_to(sc.skyoffset_frame(rotation=rotation)) assert_allclose(c_skyoffset0.pm_lon_coslat, expectedpmlonlat[0]) @@ -346,12 +393,15 @@ def test_skyoffset_two_frames_interfering(): """ # Example adapted from @bmerry's minimal example at # https://github.com/astropy/astropy/issues/11277#issuecomment-825492335 - altaz_frame = AltAz(obstime=Time('2020-04-22T13:00:00Z'), - location=EarthLocation(18, -30)) - target = SkyCoord(alt=70*u.deg, az=150*u.deg, frame=altaz_frame) - dirs_altaz_offset = SkyCoord(lon=[-0.02, 0.01, 0.0, 0.0, 0.0] * u.rad, - lat=[0.0, 0.2, 0.0, -0.3, 0.1] * u.rad, - frame=target.skyoffset_frame()) + altaz_frame = AltAz( + obstime=Time("2020-04-22T13:00:00Z"), location=EarthLocation(18, -30) + ) + target = SkyCoord(alt=70 * u.deg, az=150 * u.deg, frame=altaz_frame) + dirs_altaz_offset = SkyCoord( + lon=[-0.02, 0.01, 0.0, 0.0, 0.0] * u.rad, + lat=[0.0, 0.2, 0.0, -0.3, 0.1] * u.rad, + frame=target.skyoffset_frame(), + ) dirs_altaz = dirs_altaz_offset.transform_to(altaz_frame) dirs_icrs = dirs_altaz.transform_to(ICRS()) target_icrs = target.transform_to(ICRS()) diff --git a/astropy/coordinates/tests/test_solar_system.py b/astropy/coordinates/tests/test_solar_system.py index 721ae176458..ea249e9e2e4 100644 --- a/astropy/coordinates/tests/test_solar_system.py +++ b/astropy/coordinates/tests/test_solar_system.py @@ -32,43 +32,49 @@ if HAS_SKYFIELD: from skyfield.api import Loader, Topos -de432s_separation_tolerance_planets = 5*u.arcsec -de432s_separation_tolerance_moon = 5*u.arcsec -de432s_distance_tolerance = 20*u.km +de432s_separation_tolerance_planets = 5 * u.arcsec +de432s_separation_tolerance_moon = 5 * u.arcsec +de432s_distance_tolerance = 20 * u.km -skyfield_angular_separation_tolerance = 1*u.arcsec -skyfield_separation_tolerance = 10*u.km +skyfield_angular_separation_tolerance = 1 * u.arcsec +skyfield_separation_tolerance = 10 * u.km @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_SKYFIELD, reason='requires skyfield') +@pytest.mark.skipif(not HAS_SKYFIELD, reason="requires skyfield") def test_positions_skyfield(tmp_path): """ Test positions against those generated by skyfield. """ load = Loader(tmp_path) - t = Time('1980-03-25 00:00') + t = Time("1980-03-25 00:00") location = None # skyfield ephemeris try: - planets = load('de421.bsp') + planets = load("de421.bsp") ts = load.timescale() except OSError as e: - if os.environ.get('CI', False) and 'timed out' in str(e): - pytest.xfail('Timed out in CI') + if os.environ.get("CI", False) and "timed out" in str(e): + pytest.xfail("Timed out in CI") else: raise - mercury, jupiter, moon = planets['mercury'], planets['jupiter barycenter'], planets['moon'] - earth = planets['earth'] + mercury, jupiter, moon = ( + planets["mercury"], + planets["jupiter barycenter"], + planets["moon"], + ) + earth = planets["earth"] skyfield_t = ts.from_astropy(t) if location is not None: - earth = earth+Topos(latitude_degrees=location.lat.to_value(u.deg), - longitude_degrees=location.lon.to_value(u.deg), - elevation_m=location.height.to_value(u.m)) + earth = earth + Topos( + latitude_degrees=location.lat.to_value(u.deg), + longitude_degrees=location.lon.to_value(u.deg), + elevation_m=location.height.to_value(u.m), + ) skyfield_mercury = earth.at(skyfield_t).observe(mercury).apparent() skyfield_jupiter = earth.at(skyfield_t).observe(jupiter).apparent() @@ -79,34 +85,48 @@ def test_positions_skyfield(tmp_path): else: frame = TETE(obstime=t) - ra, dec, dist = skyfield_mercury.radec(epoch='date') - skyfield_mercury = SkyCoord(ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), - frame=frame) - ra, dec, dist = skyfield_jupiter.radec(epoch='date') - skyfield_jupiter = SkyCoord(ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), - frame=frame) - ra, dec, dist = skyfield_moon.radec(epoch='date') - skyfield_moon = SkyCoord(ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), - frame=frame) + ra, dec, dist = skyfield_mercury.radec(epoch="date") + skyfield_mercury = SkyCoord( + ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), frame=frame + ) + ra, dec, dist = skyfield_jupiter.radec(epoch="date") + skyfield_jupiter = SkyCoord( + ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), frame=frame + ) + ra, dec, dist = skyfield_moon.radec(epoch="date") + skyfield_moon = SkyCoord( + ra.to(u.deg), dec.to(u.deg), distance=dist.to(u.km), frame=frame + ) # planet positions w.r.t true equator and equinox - moon_astropy = get_moon(t, location, ephemeris='de430').transform_to(frame) - mercury_astropy = get_body('mercury', t, location, ephemeris='de430').transform_to(frame) - jupiter_astropy = get_body('jupiter', t, location, ephemeris='de430').transform_to(frame) - - assert (moon_astropy.separation(skyfield_moon) < - skyfield_angular_separation_tolerance) - assert (moon_astropy.separation_3d(skyfield_moon) < skyfield_separation_tolerance) - - assert (jupiter_astropy.separation(skyfield_jupiter) < - skyfield_angular_separation_tolerance) - assert (jupiter_astropy.separation_3d(skyfield_jupiter) < - skyfield_separation_tolerance) - - assert (mercury_astropy.separation(skyfield_mercury) < - skyfield_angular_separation_tolerance) - assert (mercury_astropy.separation_3d(skyfield_mercury) < - skyfield_separation_tolerance) + moon_astropy = get_moon(t, location, ephemeris="de430").transform_to(frame) + mercury_astropy = get_body("mercury", t, location, ephemeris="de430").transform_to( + frame + ) + jupiter_astropy = get_body("jupiter", t, location, ephemeris="de430").transform_to( + frame + ) + + assert ( + moon_astropy.separation(skyfield_moon) < skyfield_angular_separation_tolerance + ) + assert moon_astropy.separation_3d(skyfield_moon) < skyfield_separation_tolerance + + assert ( + jupiter_astropy.separation(skyfield_jupiter) + < skyfield_angular_separation_tolerance + ) + assert ( + jupiter_astropy.separation_3d(skyfield_jupiter) < skyfield_separation_tolerance + ) + + assert ( + mercury_astropy.separation(skyfield_mercury) + < skyfield_angular_separation_tolerance + ) + assert ( + mercury_astropy.separation_3d(skyfield_mercury) < skyfield_separation_tolerance + ) planets.close() @@ -117,31 +137,52 @@ class TestPositionsGeocentric: """ def setup_method(self): - self.t = Time('1980-03-25 00:00') + self.t = Time("1980-03-25 00:00") self.apparent_frame = TETE(obstime=self.t) # Results returned by JPL Horizons web interface self.horizons = { - 'mercury': SkyCoord(ra='22h41m47.78s', dec='-08d29m32.0s', - distance=c*6.323037*u.min, frame=self.apparent_frame), - 'moon': SkyCoord(ra='07h32m02.62s', dec='+18d34m05.0s', - distance=c*0.021921*u.min, frame=self.apparent_frame), - 'jupiter': SkyCoord(ra='10h17m12.82s', dec='+12d02m57.0s', - distance=c*37.694557*u.min, frame=self.apparent_frame), - 'sun': SkyCoord(ra='00h16m31.00s', dec='+01d47m16.9s', - distance=c*8.294858*u.min, frame=self.apparent_frame)} - - @pytest.mark.parametrize(('body', 'sep_tol', 'dist_tol'), - (('mercury', 7.*u.arcsec, 1000*u.km), - ('jupiter', 78.*u.arcsec, 76000*u.km), - ('moon', 20.*u.arcsec, 80*u.km), - ('sun', 5.*u.arcsec, 11.*u.km))) + "mercury": SkyCoord( + ra="22h41m47.78s", + dec="-08d29m32.0s", + distance=c * 6.323037 * u.min, + frame=self.apparent_frame, + ), + "moon": SkyCoord( + ra="07h32m02.62s", + dec="+18d34m05.0s", + distance=c * 0.021921 * u.min, + frame=self.apparent_frame, + ), + "jupiter": SkyCoord( + ra="10h17m12.82s", + dec="+12d02m57.0s", + distance=c * 37.694557 * u.min, + frame=self.apparent_frame, + ), + "sun": SkyCoord( + ra="00h16m31.00s", + dec="+01d47m16.9s", + distance=c * 8.294858 * u.min, + frame=self.apparent_frame, + ), + } + + @pytest.mark.parametrize( + ("body", "sep_tol", "dist_tol"), + ( + ("mercury", 7.0 * u.arcsec, 1000 * u.km), + ("jupiter", 78.0 * u.arcsec, 76000 * u.km), + ("moon", 20.0 * u.arcsec, 80 * u.km), + ("sun", 5.0 * u.arcsec, 11.0 * u.km), + ), + ) def test_erfa_planet(self, body, sep_tol, dist_tol): """Test predictions using erfa/plan94. Accuracies are maximum deviations listed in erfa/plan94.c, for Jupiter and Mercury, and that quoted in Meeus "Astronomical Algorithms" (1998) for the Moon. """ - astropy = get_body(body, self.t, ephemeris='builtin') + astropy = get_body(body, self.t, ephemeris="builtin") horizons = self.horizons[body] # convert to true equator and equinox @@ -151,43 +192,42 @@ def test_erfa_planet(self, body, sep_tol, dist_tol): assert astropy.separation(horizons) < sep_tol # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=dist_tol) + assert_quantity_allclose(astropy.distance, horizons.distance, atol=dist_tol) @pytest.mark.remote_data - @pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') - @pytest.mark.parametrize('body', ('mercury', 'jupiter', 'sun')) + @pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") + @pytest.mark.parametrize("body", ("mercury", "jupiter", "sun")) def test_de432s_planet(self, body): - astropy = get_body(body, self.t, ephemeris='de432s') + astropy = get_body(body, self.t, ephemeris="de432s") horizons = self.horizons[body] # convert to true equator and equinox astropy = astropy.transform_to(self.apparent_frame) # Assert sky coordinates are close. - assert (astropy.separation(horizons) < - de432s_separation_tolerance_planets) + assert astropy.separation(horizons) < de432s_separation_tolerance_planets # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=de432s_distance_tolerance) + assert_quantity_allclose( + astropy.distance, horizons.distance, atol=de432s_distance_tolerance + ) @pytest.mark.remote_data - @pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') + @pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_de432s_moon(self): - astropy = get_moon(self.t, ephemeris='de432s') - horizons = self.horizons['moon'] + astropy = get_moon(self.t, ephemeris="de432s") + horizons = self.horizons["moon"] # convert to true equator and equinox astropy = astropy.transform_to(self.apparent_frame) # Assert sky coordinates are close. - assert (astropy.separation(horizons) < - de432s_separation_tolerance_moon) + assert astropy.separation(horizons) < de432s_separation_tolerance_moon # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=de432s_distance_tolerance) + assert_quantity_allclose( + astropy.distance, horizons.distance, atol=de432s_distance_tolerance + ) class TestPositionKittPeak: @@ -197,23 +237,40 @@ class TestPositionKittPeak: """ def setup_method(self): - kitt_peak = EarthLocation.from_geodetic(lon=-111.6*u.deg, - lat=31.963333333333342*u.deg, - height=2120*u.m) - self.t = Time('2014-09-25T00:00', location=kitt_peak) + kitt_peak = EarthLocation.from_geodetic( + lon=-111.6 * u.deg, lat=31.963333333333342 * u.deg, height=2120 * u.m + ) + self.t = Time("2014-09-25T00:00", location=kitt_peak) self.apparent_frame = TETE(obstime=self.t, location=kitt_peak) # Results returned by JPL Horizons web interface self.horizons = { - 'mercury': SkyCoord(ra='13h38m58.50s', dec='-13d34m42.6s', - distance=c*7.699020*u.min, frame=self.apparent_frame), - 'moon': SkyCoord(ra='12h33m12.85s', dec='-05d17m54.4s', - distance=c*0.022054*u.min, frame=self.apparent_frame), - 'jupiter': SkyCoord(ra='09h09m55.55s', dec='+16d51m57.8s', - distance=c*49.244937*u.min, frame=self.apparent_frame)} - - @pytest.mark.parametrize(('body', 'sep_tol', 'dist_tol'), - (('mercury', 7.*u.arcsec, 500*u.km), - ('jupiter', 78.*u.arcsec, 82000*u.km))) + "mercury": SkyCoord( + ra="13h38m58.50s", + dec="-13d34m42.6s", + distance=c * 7.699020 * u.min, + frame=self.apparent_frame, + ), + "moon": SkyCoord( + ra="12h33m12.85s", + dec="-05d17m54.4s", + distance=c * 0.022054 * u.min, + frame=self.apparent_frame, + ), + "jupiter": SkyCoord( + ra="09h09m55.55s", + dec="+16d51m57.8s", + distance=c * 49.244937 * u.min, + frame=self.apparent_frame, + ), + } + + @pytest.mark.parametrize( + ("body", "sep_tol", "dist_tol"), + ( + ("mercury", 7.0 * u.arcsec, 500 * u.km), + ("jupiter", 78.0 * u.arcsec, 82000 * u.km), + ), + ) def test_erfa_planet(self, body, sep_tol, dist_tol): """Test predictions using erfa/plan94. @@ -222,7 +279,7 @@ def test_erfa_planet(self, body, sep_tol, dist_tol): # Add uncertainty in position of Earth dist_tol = dist_tol + 1300 * u.km - astropy = get_body(body, self.t, ephemeris='builtin') + astropy = get_body(body, self.t, ephemeris="builtin") horizons = self.horizons[body] # convert to true equator and equinox @@ -232,54 +289,53 @@ def test_erfa_planet(self, body, sep_tol, dist_tol): assert astropy.separation(horizons) < sep_tol # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=dist_tol) + assert_quantity_allclose(astropy.distance, horizons.distance, atol=dist_tol) @pytest.mark.remote_data - @pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') - @pytest.mark.parametrize('body', ('mercury', 'jupiter')) + @pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") + @pytest.mark.parametrize("body", ("mercury", "jupiter")) def test_de432s_planet(self, body): - astropy = get_body(body, self.t, ephemeris='de432s') + astropy = get_body(body, self.t, ephemeris="de432s") horizons = self.horizons[body] # convert to true equator and equinox astropy = astropy.transform_to(self.apparent_frame) # Assert sky coordinates are close. - assert (astropy.separation(horizons) < - de432s_separation_tolerance_planets) + assert astropy.separation(horizons) < de432s_separation_tolerance_planets # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=de432s_distance_tolerance) + assert_quantity_allclose( + astropy.distance, horizons.distance, atol=de432s_distance_tolerance + ) @pytest.mark.remote_data - @pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') + @pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_de432s_moon(self): - astropy = get_moon(self.t, ephemeris='de432s') - horizons = self.horizons['moon'] + astropy = get_moon(self.t, ephemeris="de432s") + horizons = self.horizons["moon"] # convert to true equator and equinox astropy = astropy.transform_to(self.apparent_frame) # Assert sky coordinates are close. - assert (astropy.separation(horizons) < - de432s_separation_tolerance_moon) + assert astropy.separation(horizons) < de432s_separation_tolerance_moon # Assert distances are close. - assert_quantity_allclose(astropy.distance, horizons.distance, - atol=de432s_distance_tolerance) + assert_quantity_allclose( + astropy.distance, horizons.distance, atol=de432s_distance_tolerance + ) @pytest.mark.remote_data - @pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') - @pytest.mark.parametrize('bodyname', ('mercury', 'jupiter')) + @pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") + @pytest.mark.parametrize("bodyname", ("mercury", "jupiter")) def test_custom_kernel_spec_body(self, bodyname): """ Checks that giving a kernel specifier instead of a body name works """ - coord_by_name = get_body(bodyname, self.t, ephemeris='de432s') + coord_by_name = get_body(bodyname, self.t, ephemeris="de432s") kspec = BODY_NAME_TO_KERNEL_SPEC[bodyname] - coord_by_kspec = get_body(kspec, self.t, ephemeris='de432s') + coord_by_kspec = get_body(kspec, self.t, ephemeris="de432s") assert_quantity_allclose(coord_by_name.ra, coord_by_kspec.ra) assert_quantity_allclose(coord_by_name.dec, coord_by_kspec.dec) @@ -287,7 +343,7 @@ def test_custom_kernel_spec_body(self, bodyname): @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_horizons_consistency_with_precision(): """ A test to compare at high precision against output of JPL horizons. @@ -301,42 +357,87 @@ def test_horizons_consistency_with_precision(): # JPL Horizon values for 2020_04_06 00:00 to 23:00 in 1 hour steps # JPL Horizons has a known offset (frame bias) of 51.02 mas in RA. We correct that here ra_apparent_horizons = [ - 170.167332531, 170.560688674, 170.923834838, 171.271663481, 171.620188972, 171.985340827, - 172.381766539, 172.821772139, 173.314502650, 173.865422398, 174.476108551, 175.144332386, - 175.864375310, 176.627519827, 177.422655853, 178.236955730, 179.056584831, 179.867427392, - 180.655815385, 181.409252074, 182.117113814, 182.771311578, 183.366872837, 183.902395443 + 170.167332531, + 170.560688674, + 170.923834838, + 171.271663481, + 171.620188972, + 171.985340827, + 172.381766539, + 172.821772139, + 173.314502650, + 173.865422398, + 174.476108551, + 175.144332386, + 175.864375310, + 176.627519827, + 177.422655853, + 178.236955730, + 179.056584831, + 179.867427392, + 180.655815385, + 181.409252074, + 182.117113814, + 182.771311578, + 183.366872837, + 183.902395443, ] * u.deg + 51.02376467 * u.mas dec_apparent_horizons = [ - 10.269112037, 10.058820647, 9.837152044, 9.603724551, 9.358956528, 9.104012390, 8.840674927, - 8.571162442, 8.297917326, 8.023394488, 7.749873882, 7.479312991, 7.213246666, 6.952732614, - 6.698336823, 6.450150213, 6.207828142, 5.970645962, 5.737565957, 5.507313851, 5.278462034, - 5.049521497, 4.819038911, 4.585696512 + 10.269112037, + 10.058820647, + 9.837152044, + 9.603724551, + 9.358956528, + 9.104012390, + 8.840674927, + 8.571162442, + 8.297917326, + 8.023394488, + 7.749873882, + 7.479312991, + 7.213246666, + 6.952732614, + 6.698336823, + 6.450150213, + 6.207828142, + 5.970645962, + 5.737565957, + 5.507313851, + 5.278462034, + 5.049521497, + 4.819038911, + 4.585696512, ] * u.deg - with solar_system_ephemeris.set('de430'): - loc = EarthLocation.from_geodetic(-67.787260*u.deg, -22.959748*u.deg, 5186*u.m) - times = Time('2020-04-06 00:00') + np.arange(0, 24, 1)*u.hour - astropy = get_body('moon', times, loc) + with solar_system_ephemeris.set("de430"): + loc = EarthLocation.from_geodetic( + -67.787260 * u.deg, -22.959748 * u.deg, 5186 * u.m + ) + times = Time("2020-04-06 00:00") + np.arange(0, 24, 1) * u.hour + astropy = get_body("moon", times, loc) apparent_frame = TETE(obstime=times, location=loc) astropy = astropy.transform_to(apparent_frame) - usrepr = UnitSphericalRepresentation(ra_apparent_horizons, dec_apparent_horizons) + usrepr = UnitSphericalRepresentation( + ra_apparent_horizons, dec_apparent_horizons + ) horizons = apparent_frame.realize_frame(usrepr) - assert_quantity_allclose(astropy.separation(horizons), 0*u.mas, atol=1.5*u.mas) + assert_quantity_allclose(astropy.separation(horizons), 0 * u.mas, atol=1.5 * u.mas) @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') -@pytest.mark.parametrize('time', (Time('1960-01-12 00:00'), - Time('1980-03-25 00:00'), - Time('2010-10-13 00:00'))) +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") +@pytest.mark.parametrize( + "time", + (Time("1960-01-12 00:00"), Time("1980-03-25 00:00"), Time("2010-10-13 00:00")), +) def test_get_sun_consistency(time): """ Test that the sun from JPL and the builtin get_sun match """ - sun_jpl_gcrs = get_body('sun', time, ephemeris='de432s') + sun_jpl_gcrs = get_body("sun", time, ephemeris="de432s") builtin_get_sun = get_sun(time) sep = builtin_get_sun.separation(sun_jpl_gcrs) - assert sep < 0.1*u.arcsec + assert sep < 0.1 * u.arcsec def test_get_moon_nonscalar_regression(): @@ -347,84 +448,93 @@ def test_get_moon_nonscalar_regression(): """ times = Time(["2015-08-28 03:30", "2015-09-05 10:30"]) # the following line will raise an Exception if the bug recurs. - get_moon(times, ephemeris='builtin') + get_moon(times, ephemeris="builtin") def test_barycentric_pos_posvel_same(): # Check that the two routines give identical results. - ep1 = get_body_barycentric('earth', Time('2016-03-20T12:30:00')) - ep2, _ = get_body_barycentric_posvel('earth', Time('2016-03-20T12:30:00')) + ep1 = get_body_barycentric("earth", Time("2016-03-20T12:30:00")) + ep2, _ = get_body_barycentric_posvel("earth", Time("2016-03-20T12:30:00")) assert np.all(ep1.xyz == ep2.xyz) def test_earth_barycentric_velocity_rough(): # Check that a time near the equinox gives roughly the right result. - ep, ev = get_body_barycentric_posvel('earth', Time('2016-03-20T12:30:00')) - assert_quantity_allclose(ep.xyz, [-1., 0., 0.]*u.AU, atol=0.01*u.AU) - expected = u.Quantity([0.*u.one, - np.cos(23.5*u.deg), - np.sin(23.5*u.deg)]) * -30. * u.km / u.s - assert_quantity_allclose(ev.xyz, expected, atol=1.*u.km/u.s) + ep, ev = get_body_barycentric_posvel("earth", Time("2016-03-20T12:30:00")) + assert_quantity_allclose(ep.xyz, [-1.0, 0.0, 0.0] * u.AU, atol=0.01 * u.AU) + expected = ( + u.Quantity([0.0 * u.one, np.cos(23.5 * u.deg), np.sin(23.5 * u.deg)]) + * -30.0 + * u.km + / u.s + ) + assert_quantity_allclose(ev.xyz, expected, atol=1.0 * u.km / u.s) def test_earth_barycentric_velocity_multi_d(): # Might as well test it with a multidimensional array too. - t = Time('2016-03-20T12:30:00') + np.arange(8.).reshape(2, 2, 2) * u.yr / 2. - ep, ev = get_body_barycentric_posvel('earth', t) + t = Time("2016-03-20T12:30:00") + np.arange(8.0).reshape(2, 2, 2) * u.yr / 2.0 + ep, ev = get_body_barycentric_posvel("earth", t) # note: assert_quantity_allclose doesn't like the shape mismatch. # this is a problem with np.testing.assert_allclose. - assert quantity_allclose(ep.get_xyz(xyz_axis=-1), - [[-1., 0., 0.], [+1., 0., 0.]]*u.AU, - atol=0.06*u.AU) - expected = u.Quantity([0.*u.one, - np.cos(23.5*u.deg), - np.sin(23.5*u.deg)]) * ([[-30.], [30.]] * u.km / u.s) - assert quantity_allclose(ev.get_xyz(xyz_axis=-1), expected, - atol=2.*u.km/u.s) + assert quantity_allclose( + ep.get_xyz(xyz_axis=-1), + [[-1.0, 0.0, 0.0], [+1.0, 0.0, 0.0]] * u.AU, + atol=0.06 * u.AU, + ) + expected = u.Quantity([0.0 * u.one, np.cos(23.5 * u.deg), np.sin(23.5 * u.deg)]) * ( + [[-30.0], [30.0]] * u.km / u.s + ) + assert quantity_allclose(ev.get_xyz(xyz_axis=-1), expected, atol=2.0 * u.km / u.s) @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') -@pytest.mark.parametrize(('body', 'pos_tol', 'vel_tol'), - (('mercury', 1000.*u.km, 1.*u.km/u.s), - ('jupiter', 100000.*u.km, 2.*u.km/u.s), - ('earth', 10*u.km, 10*u.mm/u.s), - ('moon', 18*u.km, 50*u.mm/u.s))) +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") +@pytest.mark.parametrize( + ("body", "pos_tol", "vel_tol"), + ( + ("mercury", 1000.0 * u.km, 1.0 * u.km / u.s), + ("jupiter", 100000.0 * u.km, 2.0 * u.km / u.s), + ("earth", 10 * u.km, 10 * u.mm / u.s), + ("moon", 18 * u.km, 50 * u.mm / u.s), + ), +) def test_barycentric_velocity_consistency(body, pos_tol, vel_tol): # Tolerances are about 1.5 times the rms listed for plan94 and epv00, # except for Mercury (which nominally is 334 km rms), and the Moon # (which nominally is 6 km rms). - t = Time('2016-03-20T12:30:00') - ep, ev = get_body_barycentric_posvel(body, t, ephemeris='builtin') - dp, dv = get_body_barycentric_posvel(body, t, ephemeris='de432s') + t = Time("2016-03-20T12:30:00") + ep, ev = get_body_barycentric_posvel(body, t, ephemeris="builtin") + dp, dv = get_body_barycentric_posvel(body, t, ephemeris="de432s") assert_quantity_allclose(ep.xyz, dp.xyz, atol=pos_tol) assert_quantity_allclose(ev.xyz, dv.xyz, atol=vel_tol) # Might as well test it with a multidimensional array too. - t = Time('2016-03-20T12:30:00') + np.arange(8.).reshape(2, 2, 2) * u.yr / 2. - ep, ev = get_body_barycentric_posvel(body, t, ephemeris='builtin') - dp, dv = get_body_barycentric_posvel(body, t, ephemeris='de432s') + t = Time("2016-03-20T12:30:00") + np.arange(8.0).reshape(2, 2, 2) * u.yr / 2.0 + ep, ev = get_body_barycentric_posvel(body, t, ephemeris="builtin") + dp, dv = get_body_barycentric_posvel(body, t, ephemeris="de432s") assert_quantity_allclose(ep.xyz, dp.xyz, atol=pos_tol) assert_quantity_allclose(ev.xyz, dv.xyz, atol=vel_tol) @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') -@pytest.mark.parametrize('time', (Time('1960-01-12 00:00'), - Time('1980-03-25 00:00'), - Time('2010-10-13 00:00'))) +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") +@pytest.mark.parametrize( + "time", + (Time("1960-01-12 00:00"), Time("1980-03-25 00:00"), Time("2010-10-13 00:00")), +) def test_url_or_file_ephemeris(time): # URL for ephemeris de432s used for testing: - url = 'http://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de432s.bsp' + url = "http://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de432s.bsp" # Pass the ephemeris directly as a URL. - coord_by_url = get_body('earth', time, ephemeris=url) + coord_by_url = get_body("earth", time, ephemeris=url) # Translate the URL to the cached location on the filesystem. # Since we just used the url above, it should already have been downloaded. filepath = download_file(url, cache=True) # Get the coordinates using the file path directly: - coord_by_filepath = get_body('earth', time, ephemeris=filepath) + coord_by_filepath = get_body("earth", time, ephemeris=filepath) # Using the URL or filepath should give exactly the same results: assert_quantity_allclose(coord_by_url.ra, coord_by_filepath.ra) @@ -433,45 +543,53 @@ def test_url_or_file_ephemeris(time): @pytest.mark.remote_data -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_url_ephemeris_wrong_input(): - time = Time('1960-01-12 00:00') + time = Time("1960-01-12 00:00") with pytest.raises((HTTPError, URLError)): # A non-existent URL - get_body('earth', time, ephemeris=get_pkg_data_filename('path/to/nonexisting/file.bsp')) + get_body( + "earth", + time, + ephemeris=get_pkg_data_filename("path/to/nonexisting/file.bsp"), + ) with pytest.raises(HTTPError): # A non-existent version of the JPL ephemeris - get_body('earth', time, ephemeris='de001') + get_body("earth", time, ephemeris="de001") with pytest.raises(ValueError): # An invalid string - get_body('earth', time, ephemeris='not_an_ephemeris') + get_body("earth", time, ephemeris="not_an_ephemeris") -@pytest.mark.skipif(not HAS_JPLEPHEM, reason='requires jplephem') +@pytest.mark.skipif(not HAS_JPLEPHEM, reason="requires jplephem") def test_file_ephemeris_wrong_input(): - time = Time('1960-01-12 00:00') + time = Time("1960-01-12 00:00") # Try loading a non-existing file: with pytest.raises(ValueError): - get_body('earth', time, ephemeris='/path/to/nonexisting/file.bsp') + get_body("earth", time, ephemeris="/path/to/nonexisting/file.bsp") # NOTE: This test currently leaves the file open (ResourceWarning). # To fix this issue, an upstream fix is required in jplephem # package. # Try loading a file that does exist, but is not an ephemeris file: with pytest.warns(ResourceWarning), pytest.raises(ValueError): - get_body('earth', time, ephemeris=__file__) + get_body("earth", time, ephemeris=__file__) def test_regression_10271(): - t = Time(58973.534052125986, format='mjd') + t = Time(58973.534052125986, format="mjd") # GCRS position of ALMA at this time - obs_p = CartesianRepresentation(5724535.74068625, -1311071.58985697, -2492738.93017009, u.m) + obs_p = CartesianRepresentation( + 5724535.74068625, -1311071.58985697, -2492738.93017009, u.m + ) geocentre = CartesianRepresentation(0, 0, 0, u.m) - icrs_sun_from_alma = _get_apparent_body_position('sun', t, 'builtin', obs_p) - icrs_sun_from_geocentre = _get_apparent_body_position('sun', t, 'builtin', geocentre) + icrs_sun_from_alma = _get_apparent_body_position("sun", t, "builtin", obs_p) + icrs_sun_from_geocentre = _get_apparent_body_position( + "sun", t, "builtin", geocentre + ) difference = (icrs_sun_from_alma - icrs_sun_from_geocentre).norm() - assert_quantity_allclose(difference, 0.13046941*u.m, atol=1*u.mm) + assert_quantity_allclose(difference, 0.13046941 * u.m, atol=1 * u.mm) diff --git a/astropy/coordinates/tests/test_spectral_coordinate.py b/astropy/coordinates/tests/test_spectral_coordinate.py index 30e6ec010e4..b5111b6c7a6 100644 --- a/astropy/coordinates/tests/test_spectral_coordinate.py +++ b/astropy/coordinates/tests/test_spectral_coordinate.py @@ -31,9 +31,14 @@ from astropy.wcs.wcsapi.fitswcs import VELOCITY_FRAMES as FITSWCS_VELOCITY_FRAMES -def assert_frame_allclose(frame1, frame2, - pos_rtol=1e-7, pos_atol=1 * u.m, - vel_rtol=1e-7, vel_atol=1 * u.mm / u.s): +def assert_frame_allclose( + frame1, + frame2, + pos_rtol=1e-7, + pos_atol=1 * u.m, + vel_rtol=1e-7, + vel_atol=1 * u.mm / u.s, +): # checks that: # - the positions are equal to within some tolerance (the relative tolerance # should be dimensionless, the absolute tolerance should be a distance). @@ -42,9 +47,9 @@ def assert_frame_allclose(frame1, frame2, # the other one can have zero velocities # - if velocities are present, they are equal to some tolerance # Ideally this should accept both frames and SkyCoords - if hasattr(frame1, 'frame'): # SkyCoord-like + if hasattr(frame1, "frame"): # SkyCoord-like frame1 = frame1.frame - if hasattr(frame2, 'frame'): # SkyCoord-like + if hasattr(frame2, "frame"): # SkyCoord-like frame2 = frame2.frame # assert (frame1.data.differentials and frame2.data.differentials or @@ -53,11 +58,17 @@ def assert_frame_allclose(frame1, frame2, frame2_in_1 = frame2.transform_to(frame1) - assert_quantity_allclose(0 * u.m, frame1.separation_3d(frame2_in_1), rtol=pos_rtol, atol=pos_atol) + assert_quantity_allclose( + 0 * u.m, frame1.separation_3d(frame2_in_1), rtol=pos_rtol, atol=pos_atol + ) if frame1.data.differentials: - d1 = frame1.data.represent_as(CartesianRepresentation, CartesianDifferential).differentials['s'] - d2 = frame2_in_1.data.represent_as(CartesianRepresentation, CartesianDifferential).differentials['s'] + d1 = frame1.data.represent_as( + CartesianRepresentation, CartesianDifferential + ).differentials["s"] + d2 = frame2_in_1.data.represent_as( + CartesianRepresentation, CartesianDifferential + ).differentials["s"] assert_quantity_allclose(d1.norm(d1), d1.norm(d2), rtol=vel_rtol, atol=vel_atol) @@ -65,7 +76,7 @@ def assert_frame_allclose(frame1, frame2, @pytest.fixture(scope="module") def greenwich_earthlocation(request): if ( - not hasattr(EarthLocation, '_site_registry') + not hasattr(EarthLocation, "_site_registry") and request.config.getoption("remote_data") == "none" ): EarthLocation._get_site_registry(force_builtin=True) @@ -80,16 +91,23 @@ def greenwich_earthlocation(request): # frame or representation class. # Local Standard of Rest -LSRD = Galactic(u=0.1 * u.km, v=0.1 * u.km, w=0.1 * u.km, - U=9 * u.km / u.s, V=12 * u.km / u.s, W=7 * u.km / u.s, - representation_type='cartesian', differential_type='cartesian') +LSRD = Galactic( + u=0.1 * u.km, + v=0.1 * u.km, + w=0.1 * u.km, + U=9 * u.km / u.s, + V=12 * u.km / u.s, + W=7 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", +) LSRD_EQUIV = [ - LSRD, - SkyCoord(LSRD), # as a SkyCoord - LSRD.transform_to(ICRS()), # different frame - LSRD.transform_to(ICRS()).transform_to(Galactic()) # different representation - ] + LSRD, + SkyCoord(LSRD), # as a SkyCoord + LSRD.transform_to(ICRS()), # different frame + LSRD.transform_to(ICRS()).transform_to(Galactic()), # different representation +] @pytest.fixture(params=[None] + LSRD_EQUIV) @@ -98,15 +116,17 @@ def observer(request): # Target located in direction of motion of LSRD with no velocities -LSRD_DIR_STATIONARY = Galactic(u=9 * u.km, v=12 * u.km, w=7 * u.km, - representation_type='cartesian') +LSRD_DIR_STATIONARY = Galactic( + u=9 * u.km, v=12 * u.km, w=7 * u.km, representation_type="cartesian" +) LSRD_DIR_STATIONARY_EQUIV = [ - LSRD_DIR_STATIONARY, - SkyCoord(LSRD_DIR_STATIONARY), # as a SkyCoord - LSRD_DIR_STATIONARY.transform_to(FK5()), # different frame - LSRD_DIR_STATIONARY.transform_to(ICRS()).transform_to(Galactic()) # different representation - ] + LSRD_DIR_STATIONARY, + SkyCoord(LSRD_DIR_STATIONARY), # as a SkyCoord + LSRD_DIR_STATIONARY.transform_to(FK5()), # different frame + # different representation + LSRD_DIR_STATIONARY.transform_to(ICRS()).transform_to(Galactic()), +] @pytest.fixture(params=[None] + LSRD_DIR_STATIONARY_EQUIV) @@ -115,8 +135,9 @@ def target(request): def test_create_spectral_coord_observer_target(observer, target): - - with nullcontext() if target is None else pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): + with nullcontext() if target is None else pytest.warns( + AstropyUserWarning, match="No velocity defined on frame" + ): coord = SpectralCoord([100, 200, 300] * u.nm, observer=observer, target=target) if observer is None: @@ -134,10 +155,13 @@ def test_create_spectral_coord_observer_target(observer, target): if observer is None or target is None: assert quantity_allclose(coord.redshift, 0) - assert quantity_allclose(coord.radial_velocity, 0 * u.km/u.s) - elif (any(observer is lsrd for lsrd in LSRD_EQUIV) - and any(target is lsrd for lsrd in LSRD_DIR_STATIONARY_EQUIV)): - assert_quantity_allclose(coord.radial_velocity, -274 ** 0.5 * u.km / u.s, atol=1e-4 * u.km / u.s) + assert quantity_allclose(coord.radial_velocity, 0 * u.km / u.s) + elif any(observer is lsrd for lsrd in LSRD_EQUIV) and any( + target is lsrd for lsrd in LSRD_DIR_STATIONARY_EQUIV + ): + assert_quantity_allclose( + coord.radial_velocity, -(274**0.5) * u.km / u.s, atol=1e-4 * u.km / u.s + ) assert_quantity_allclose(coord.redshift, -5.5213158163147646e-05, atol=1e-9) else: raise NotImplementedError() @@ -147,10 +171,16 @@ def test_create_from_spectral_coord(observer, target): """ Checks that parameters are correctly copied to the new SpectralCoord object """ - with nullcontext() if target is None else pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - spec_coord1 = SpectralCoord([100, 200, 300] * u.nm, observer=observer, - target=target, doppler_convention='optical', - doppler_rest=6000*u.AA) + with nullcontext() if target is None else pytest.warns( + AstropyUserWarning, match="No velocity defined on frame" + ): + spec_coord1 = SpectralCoord( + [100, 200, 300] * u.nm, + observer=observer, + target=target, + doppler_convention="optical", + doppler_rest=6000 * u.AA, + ) spec_coord2 = SpectralCoord(spec_coord1) assert spec_coord1.observer == spec_coord2.observer assert spec_coord1.target == spec_coord2.target @@ -163,11 +193,10 @@ def test_create_from_spectral_coord(observer, target): def test_apply_relativistic_doppler_shift(): - # Frequency sq1 = SpectralQuantity(1 * u.GHz) sq2 = _apply_relativistic_doppler_shift(sq1, 0.5 * c) - assert_quantity_allclose(sq2, np.sqrt(1. / 3.) * u.GHz) + assert_quantity_allclose(sq2, np.sqrt(1.0 / 3.0) * u.GHz) # Wavelength sq3 = SpectralQuantity(500 * u.nm) @@ -177,48 +206,56 @@ def test_apply_relativistic_doppler_shift(): # Energy sq5 = SpectralQuantity(300 * u.eV) sq6 = _apply_relativistic_doppler_shift(sq5, 0.5 * c) - assert_quantity_allclose(sq6, np.sqrt(1. / 3.) * 300 * u.eV) + assert_quantity_allclose(sq6, np.sqrt(1.0 / 3.0) * 300 * u.eV) # Wavenumber sq7 = SpectralQuantity(0.01 / u.micron) sq8 = _apply_relativistic_doppler_shift(sq7, 0.5 * c) - assert_quantity_allclose(sq8, np.sqrt(1. / 3.) * 0.01 / u.micron) + assert_quantity_allclose(sq8, np.sqrt(1.0 / 3.0) * 0.01 / u.micron) # Velocity (doppler_convention='relativistic') - sq9 = SpectralQuantity(200 * u.km / u.s, doppler_convention='relativistic', doppler_rest=1 * u.GHz) + sq9 = SpectralQuantity( + 200 * u.km / u.s, doppler_convention="relativistic", doppler_rest=1 * u.GHz + ) sq10 = _apply_relativistic_doppler_shift(sq9, 300 * u.km / u.s) assert_quantity_allclose(sq10, 499.999666 * u.km / u.s) - assert sq10.doppler_convention == 'relativistic' + assert sq10.doppler_convention == "relativistic" # Velocity (doppler_convention='optical') - sq11 = SpectralQuantity(200 * u.km / u.s, doppler_convention='radio', doppler_rest=1 * u.GHz) + sq11 = SpectralQuantity( + 200 * u.km / u.s, doppler_convention="radio", doppler_rest=1 * u.GHz + ) sq12 = _apply_relativistic_doppler_shift(sq11, 300 * u.km / u.s) assert_quantity_allclose(sq12, 499.650008 * u.km / u.s) - assert sq12.doppler_convention == 'radio' + assert sq12.doppler_convention == "radio" # Velocity (doppler_convention='radio') - sq13 = SpectralQuantity(200 * u.km / u.s, doppler_convention='optical', doppler_rest=1 * u.GHz) + sq13 = SpectralQuantity( + 200 * u.km / u.s, doppler_convention="optical", doppler_rest=1 * u.GHz + ) sq14 = _apply_relativistic_doppler_shift(sq13, 300 * u.km / u.s) assert_quantity_allclose(sq14, 500.350493 * u.km / u.s) - assert sq14.doppler_convention == 'optical' + assert sq14.doppler_convention == "optical" # Velocity - check relativistic velocity addition - sq13 = SpectralQuantity(0 * u.km / u.s, doppler_convention='relativistic', doppler_rest=1 * u.GHz) + sq13 = SpectralQuantity( + 0 * u.km / u.s, doppler_convention="relativistic", doppler_rest=1 * u.GHz + ) sq14 = _apply_relativistic_doppler_shift(sq13, 0.999 * c) assert_quantity_allclose(sq14, 0.999 * c) sq14 = _apply_relativistic_doppler_shift(sq14, 0.999 * c) assert_quantity_allclose(sq14, (0.999 * 2) / (1 + 0.999**2) * c) - assert sq14.doppler_convention == 'relativistic' + assert sq14.doppler_convention == "relativistic" # Cases that should raise errors sq15 = SpectralQuantity(200 * u.km / u.s) - with pytest.raises(ValueError, match='doppler_convention not set'): + with pytest.raises(ValueError, match="doppler_convention not set"): _apply_relativistic_doppler_shift(sq15, 300 * u.km / u.s) sq16 = SpectralQuantity(200 * u.km / u.s, doppler_rest=10 * u.GHz) - with pytest.raises(ValueError, match='doppler_convention not set'): + with pytest.raises(ValueError, match="doppler_convention not set"): _apply_relativistic_doppler_shift(sq16, 300 * u.km / u.s) - sq17 = SpectralQuantity(200 * u.km / u.s, doppler_convention='optical') - with pytest.raises(ValueError, match='doppler_rest not set'): + sq17 = SpectralQuantity(200 * u.km / u.s, doppler_convention="optical") + with pytest.raises(ValueError, match="doppler_rest not set"): _apply_relativistic_doppler_shift(sq17, 300 * u.km / u.s) @@ -227,7 +264,7 @@ def test_apply_relativistic_doppler_shift(): def test_init_quantity(): sc = SpectralCoord(10 * u.GHz) - assert sc.value == 10. + assert sc.value == 10.0 assert sc.unit is u.GHz assert sc.doppler_convention is None assert sc.doppler_rest is None @@ -236,51 +273,76 @@ def test_init_quantity(): def test_init_spectral_quantity(): - sc = SpectralCoord(SpectralQuantity(10 * u.GHz, doppler_convention='optical')) - assert sc.value == 10. + sc = SpectralCoord(SpectralQuantity(10 * u.GHz, doppler_convention="optical")) + assert sc.value == 10.0 assert sc.unit is u.GHz - assert sc.doppler_convention == 'optical' + assert sc.doppler_convention == "optical" assert sc.doppler_rest is None assert sc.observer is None assert sc.target is None def test_init_too_many_args(): + with pytest.raises( + ValueError, match="Cannot specify radial velocity or redshift if both" + ): + SpectralCoord( + 10 * u.GHz, + observer=LSRD, + target=SkyCoord(10, 20, unit="deg"), + radial_velocity=1 * u.km / u.s, + ) + + with pytest.raises( + ValueError, match="Cannot specify radial velocity or redshift if both" + ): + SpectralCoord( + 10 * u.GHz, observer=LSRD, target=SkyCoord(10, 20, unit="deg"), redshift=1 + ) - with pytest.raises(ValueError, match='Cannot specify radial velocity or redshift if both'): - SpectralCoord(10 * u.GHz, observer=LSRD, target=SkyCoord(10, 20, unit='deg'), - radial_velocity=1 * u.km / u.s) - - with pytest.raises(ValueError, match='Cannot specify radial velocity or redshift if both'): - SpectralCoord(10 * u.GHz, observer=LSRD, target=SkyCoord(10, 20, unit='deg'), - redshift=1) - - with pytest.raises(ValueError, match='Cannot set both a radial velocity and redshift'): + with pytest.raises( + ValueError, match="Cannot set both a radial velocity and redshift" + ): SpectralCoord(10 * u.GHz, radial_velocity=1 * u.km / u.s, redshift=1) def test_init_wrong_type(): - - with pytest.raises(TypeError, match='observer must be a SkyCoord or coordinate frame instance'): + with pytest.raises( + TypeError, match="observer must be a SkyCoord or coordinate frame instance" + ): SpectralCoord(10 * u.GHz, observer=3.4) - with pytest.raises(TypeError, match='target must be a SkyCoord or coordinate frame instance'): + with pytest.raises( + TypeError, match="target must be a SkyCoord or coordinate frame instance" + ): SpectralCoord(10 * u.GHz, target=3.4) - with pytest.raises(u.UnitsError, match="Argument 'radial_velocity' to function " - "'__new__' must be in units convertible to 'km / s'"): + with pytest.raises( + u.UnitsError, + match=( + "Argument 'radial_velocity' to function " + "'__new__' must be in units convertible to 'km / s'" + ), + ): SpectralCoord(10 * u.GHz, radial_velocity=1 * u.kg) - with pytest.raises(TypeError, match="Argument 'radial_velocity' to function " - "'__new__' has no 'unit' attribute. You should " - "pass in an astropy Quantity instead."): - SpectralCoord(10 * u.GHz, radial_velocity='banana') + with pytest.raises( + TypeError, + match=( + "Argument 'radial_velocity' to function '__new__' has no 'unit' attribute." + " You should pass in an astropy Quantity instead." + ), + ): + SpectralCoord(10 * u.GHz, radial_velocity="banana") - with pytest.raises(u.UnitsError, match='redshift should be dimensionless'): + with pytest.raises(u.UnitsError, match="redshift should be dimensionless"): SpectralCoord(10 * u.GHz, redshift=1 * u.m) - with pytest.raises(TypeError, match='Cannot parse "banana" as a Quantity. It does not start with a number.'): - SpectralCoord(10 * u.GHz, redshift='banana') + with pytest.raises( + TypeError, + match='Cannot parse "banana" as a Quantity. It does not start with a number.', + ): + SpectralCoord(10 * u.GHz, redshift="banana") def test_observer_init_rv_behavior(): @@ -289,68 +351,68 @@ def test_observer_init_rv_behavior(): """ # Start off by specifying the radial velocity only - sc_init = SpectralCoord([4000, 5000]*u.AA, - radial_velocity=100*u.km/u.s) + sc_init = SpectralCoord([4000, 5000] * u.AA, radial_velocity=100 * u.km / u.s) assert sc_init.observer is None assert sc_init.target is None - assert_quantity_allclose(sc_init.radial_velocity, 100*u.km/u.s) + assert_quantity_allclose(sc_init.radial_velocity, 100 * u.km / u.s) # Next, set the observer, and check that the radial velocity hasn't changed - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc_init.observer = ICRS(CartesianRepresentation([0*u.km, 0*u.km, 0*u.km])) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc_init.observer = ICRS(CartesianRepresentation([0 * u.km, 0 * u.km, 0 * u.km])) assert sc_init.observer is not None - assert_quantity_allclose(sc_init.radial_velocity, 100*u.km/u.s) + assert_quantity_allclose(sc_init.radial_velocity, 100 * u.km / u.s) # Setting the target should now cause the original radial velocity to be # dropped in favor of the automatically computed one - sc_init.target = SkyCoord(CartesianRepresentation([1*u.km, 0*u.km, 0*u.km]), - frame='icrs', radial_velocity=30 * u.km / u.s) + sc_init.target = SkyCoord( + CartesianRepresentation([1 * u.km, 0 * u.km, 0 * u.km]), + frame="icrs", + radial_velocity=30 * u.km / u.s, + ) assert sc_init.target is not None assert_quantity_allclose(sc_init.radial_velocity, 30 * u.km / u.s) # The observer can only be set if originally None - now that it isn't # setting it again should fail - with pytest.raises(ValueError, match='observer has already been set'): - sc_init.observer = GCRS(CartesianRepresentation([0*u.km, 1*u.km, 0*u.km])) + with pytest.raises(ValueError, match="observer has already been set"): + sc_init.observer = GCRS(CartesianRepresentation([0 * u.km, 1 * u.km, 0 * u.km])) # And similarly, changing the target should not be possible - with pytest.raises(ValueError, match='target has already been set'): - sc_init.target = GCRS(CartesianRepresentation([0*u.km, 1*u.km, 0*u.km])) + with pytest.raises(ValueError, match="target has already been set"): + sc_init.target = GCRS(CartesianRepresentation([0 * u.km, 1 * u.km, 0 * u.km])) def test_rv_redshift_initialization(): - # Check that setting the redshift sets the radial velocity appropriately, # and that the redshift can be recovered - sc_init = SpectralCoord([4000, 5000]*u.AA, redshift=1) + sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=1) assert isinstance(sc_init.redshift, u.Quantity) - assert_quantity_allclose(sc_init.redshift, 1*u.dimensionless_unscaled) + assert_quantity_allclose(sc_init.redshift, 1 * u.dimensionless_unscaled) assert_quantity_allclose(sc_init.radial_velocity, 0.6 * c) # Check that setting the same radial velocity produces the same redshift # and that the radial velocity can be recovered - sc_init2 = SpectralCoord([4000, 5000]*u.AA, radial_velocity=0.6 * c) - assert_quantity_allclose(sc_init2.redshift, 1*u.dimensionless_unscaled) + sc_init2 = SpectralCoord([4000, 5000] * u.AA, radial_velocity=0.6 * c) + assert_quantity_allclose(sc_init2.redshift, 1 * u.dimensionless_unscaled) assert_quantity_allclose(sc_init2.radial_velocity, 0.6 * c) # Check that specifying redshift as a quantity works - sc_init3 = SpectralCoord([4000, 5000]*u.AA, redshift=1 * u.one) + sc_init3 = SpectralCoord([4000, 5000] * u.AA, redshift=1 * u.one) assert sc_init.redshift == sc_init3.redshift # Make sure that both redshift and radial velocity can't be specified at # the same time. - with pytest.raises(ValueError, match='Cannot set both a radial velocity and redshift'): - SpectralCoord([4000, 5000]*u.AA, - radial_velocity=10*u.km/u.s, - redshift=2) + with pytest.raises( + ValueError, match="Cannot set both a radial velocity and redshift" + ): + SpectralCoord([4000, 5000] * u.AA, radial_velocity=10 * u.km / u.s, redshift=2) def test_replicate(): - # The replicate method makes a new object with attributes updated, but doesn't # do any conversion - sc_init = SpectralCoord([4000, 5000]*u.AA, redshift=2) + sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_set_rv = sc_init.replicate(redshift=1) assert_quantity_allclose(sc_set_rv.radial_velocity, 0.6 * c) @@ -360,132 +422,207 @@ def test_replicate(): assert_quantity_allclose(sc_set_rv.redshift, np.sqrt(3) - 1) assert_quantity_allclose(sc_init, [4000, 5000] * u.AA) - gcrs_origin = GCRS(CartesianRepresentation([0*u.km, 0*u.km, 0*u.km])) - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc_init2 = SpectralCoord([4000, 5000]*u.AA, redshift=1, - observer=gcrs_origin) - with np.errstate(all='ignore'): - sc_init2.replicate(redshift=.5) + gcrs_origin = GCRS(CartesianRepresentation([0 * u.km, 0 * u.km, 0 * u.km])) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc_init2 = SpectralCoord([4000, 5000] * u.AA, redshift=1, observer=gcrs_origin) + with np.errstate(all="ignore"): + sc_init2.replicate(redshift=0.5) assert_quantity_allclose(sc_init2, [4000, 5000] * u.AA) - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc_init3 = SpectralCoord([4000, 5000]*u.AA, redshift=1, - target=gcrs_origin) - with np.errstate(all='ignore'): - sc_init3.replicate(redshift=.5) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc_init3 = SpectralCoord([4000, 5000] * u.AA, redshift=1, target=gcrs_origin) + with np.errstate(all="ignore"): + sc_init3.replicate(redshift=0.5) assert_quantity_allclose(sc_init2, [4000, 5000] * u.AA) - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc_init4 = SpectralCoord([4000, 5000]*u.AA, - observer=gcrs_origin, target=gcrs_origin) - with pytest.raises(ValueError, match='Cannot specify radial velocity or redshift if both target and observer are specified'): - sc_init4.replicate(redshift=.5) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc_init4 = SpectralCoord( + [4000, 5000] * u.AA, observer=gcrs_origin, target=gcrs_origin + ) + with pytest.raises( + ValueError, + match=( + "Cannot specify radial velocity or redshift if both target and observer are" + " specified" + ), + ): + sc_init4.replicate(redshift=0.5) - sc_init = SpectralCoord([4000, 5000]*u.AA, redshift=2) + sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_init_copy = sc_init.replicate(copy=True) sc_init[0] = 6000 * u.AA assert_quantity_allclose(sc_init_copy, [4000, 5000] * u.AA) - sc_init = SpectralCoord([4000, 5000]*u.AA, redshift=2) + sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_init_ref = sc_init.replicate() sc_init[0] = 6000 * u.AA assert_quantity_allclose(sc_init_ref, [6000, 5000] * u.AA) def test_with_observer_stationary_relative_to(): - # Simple tests of with_observer_stationary_relative_to to cover different # ways of calling it # The replicate method makes a new object with attributes updated, but doesn't # do any conversion - sc1 = SpectralCoord([4000, 5000]*u.AA) - with pytest.raises(ValueError, match='This method can only be used if both ' - 'observer and target are defined on the ' - 'SpectralCoord'): - sc1.with_observer_stationary_relative_to('icrs') - - sc2 = SpectralCoord([4000, 5000] * u.AA, - observer=ICRS(0 * u.km, 0 * u.km, 0 * u.km, - -1 * u.km / u.s, 0 * u.km / u.s, -1 * u.km / u.s, - representation_type='cartesian', - differential_type='cartesian'), - target=ICRS(0 * u.deg, 45 * u.deg, distance=1 * u.kpc, radial_velocity=2 * u.km / u.s)) + sc1 = SpectralCoord([4000, 5000] * u.AA) + with pytest.raises( + ValueError, + match=( + "This method can only be used if both observer and target are defined on" + " the SpectralCoord" + ), + ): + sc1.with_observer_stationary_relative_to("icrs") + + sc2 = SpectralCoord( + [4000, 5000] * u.AA, + observer=ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + -1 * u.km / u.s, + 0 * u.km / u.s, + -1 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", + ), + target=ICRS( + 0 * u.deg, 45 * u.deg, distance=1 * u.kpc, radial_velocity=2 * u.km / u.s + ), + ) # Motion of observer is in opposite direction to target - assert_quantity_allclose(sc2.radial_velocity, (2 + 2 ** 0.5) * u.km / u.s) + assert_quantity_allclose(sc2.radial_velocity, (2 + 2**0.5) * u.km / u.s) # Change to observer that is stationary in ICRS - sc3 = sc2.with_observer_stationary_relative_to('icrs') + sc3 = sc2.with_observer_stationary_relative_to("icrs") # Velocity difference is now pure radial velocity of target assert_quantity_allclose(sc3.radial_velocity, 2 * u.km / u.s) # Check setting the velocity in with_observer_stationary_relative_to - sc4 = sc2.with_observer_stationary_relative_to('icrs', velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + sc4 = sc2.with_observer_stationary_relative_to( + "icrs", velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s + ) # Observer once again moving away from target but faster assert_quantity_allclose(sc4.radial_velocity, 4 * u.km / u.s) # Check that we can also pass frame classes instead of names - sc5 = sc2.with_observer_stationary_relative_to(ICRS, velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + sc5 = sc2.with_observer_stationary_relative_to( + ICRS, velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s + ) assert_quantity_allclose(sc5.radial_velocity, 4 * u.km / u.s) # And make sure we can also pass instances of classes without data - sc6 = sc2.with_observer_stationary_relative_to(ICRS(), velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + sc6 = sc2.with_observer_stationary_relative_to( + ICRS(), velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s + ) assert_quantity_allclose(sc6.radial_velocity, 4 * u.km / u.s) # And with data provided no velocities are present - sc7 = sc2.with_observer_stationary_relative_to(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - representation_type='cartesian'), - velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + sc7 = sc2.with_observer_stationary_relative_to( + ICRS(0 * u.km, 0 * u.km, 0 * u.km, representation_type="cartesian"), + velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s, + ) assert_quantity_allclose(sc7.radial_velocity, 4 * u.km / u.s) # And also have the ability to pass frames with velocities already defined - sc8 = sc2.with_observer_stationary_relative_to(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, - representation_type='cartesian', - differential_type='cartesian')) - assert_quantity_allclose(sc8.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s) + sc8 = sc2.with_observer_stationary_relative_to( + ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + 2**0.5 * u.km / u.s, + 0 * u.km / u.s, + 2**0.5 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", + ) + ) + assert_quantity_allclose( + sc8.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s + ) # Make sure that things work properly if passing a SkyCoord - sc9 = sc2.with_observer_stationary_relative_to(SkyCoord(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - representation_type='cartesian')), - velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + sc9 = sc2.with_observer_stationary_relative_to( + SkyCoord(ICRS(0 * u.km, 0 * u.km, 0 * u.km, representation_type="cartesian")), + velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s, + ) assert_quantity_allclose(sc9.radial_velocity, 4 * u.km / u.s) - sc10 = sc2.with_observer_stationary_relative_to(SkyCoord(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, - representation_type='cartesian', - differential_type='cartesian'))) - assert_quantity_allclose(sc10.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s) + sc10 = sc2.with_observer_stationary_relative_to( + SkyCoord( + ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + 2**0.5 * u.km / u.s, + 0 * u.km / u.s, + 2**0.5 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", + ) + ) + ) + assert_quantity_allclose( + sc10.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s + ) # But we shouldn't be able to pass both a frame with velocities, and explicit velocities - with pytest.raises(ValueError, match='frame already has differentials, cannot also specify velocity'): - sc2.with_observer_stationary_relative_to(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, - representation_type='cartesian', - differential_type='cartesian'), - velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) + with pytest.raises( + ValueError, + match="frame already has differentials, cannot also specify velocity", + ): + sc2.with_observer_stationary_relative_to( + ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + 2**0.5 * u.km / u.s, + 0 * u.km / u.s, + 2**0.5 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", + ), + velocity=[-(2**0.5), 0, -(2**0.5)] * u.km / u.s, + ) # And velocities should have three elements - with pytest.raises(ValueError, match='velocity should be a Quantity vector with 3 elements'): - sc2.with_observer_stationary_relative_to(ICRS, velocity=[-2**0.5, 0, -2**0.5, -3] * u.km / u.s) + with pytest.raises( + ValueError, match="velocity should be a Quantity vector with 3 elements" + ): + sc2.with_observer_stationary_relative_to( + ICRS, velocity=[-(2**0.5), 0, -(2**0.5), -3] * u.km / u.s + ) # Make sure things don't change depending on what frame class is used for reference - sc11 = sc2.with_observer_stationary_relative_to(SkyCoord(ICRS(0 * u.km, 0 * u.km, 0 * u.km, - 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, - representation_type='cartesian', - differential_type='cartesian')).transform_to(Galactic)) - assert_quantity_allclose(sc11.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s) + sc11 = sc2.with_observer_stationary_relative_to( + SkyCoord( + ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + 2**0.5 * u.km / u.s, + 0 * u.km / u.s, + 2**0.5 * u.km / u.s, + representation_type="cartesian", + differential_type="cartesian", + ) + ).transform_to(Galactic) + ) + assert_quantity_allclose( + sc11.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s + ) # Check that it is possible to preserve the observer frame sc12 = sc2.with_observer_stationary_relative_to(LSRD) @@ -496,7 +633,6 @@ def test_with_observer_stationary_relative_to(): def test_los_shift_radial_velocity(): - # Tests to make sure that with_radial_velocity_shift correctly calculates # the new radial velocity @@ -510,8 +646,10 @@ def test_los_shift_radial_velocity(): sc3 = sc1.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc3.radial_velocity, -2 * u.km / u.s) - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc4 = SpectralCoord(500 * u.nm, radial_velocity=1 * u.km / u.s, observer=gcrs_not_origin) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc4 = SpectralCoord( + 500 * u.nm, radial_velocity=1 * u.km / u.s, observer=gcrs_not_origin + ) sc5 = sc4.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc5.radial_velocity, 2 * u.km / u.s) @@ -519,8 +657,12 @@ def test_los_shift_radial_velocity(): sc6 = sc4.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc6.radial_velocity, -2 * u.km / u.s) - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc7 = SpectralCoord(500 * u.nm, radial_velocity=1 * u.km / u.s, target=ICRS(10 * u.deg, 20 * u.deg)) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc7 = SpectralCoord( + 500 * u.nm, + radial_velocity=1 * u.km / u.s, + target=ICRS(10 * u.deg, 20 * u.deg), + ) sc8 = sc7.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc8.radial_velocity, 2 * u.km / u.s) @@ -530,12 +672,17 @@ def test_los_shift_radial_velocity(): # Check that things still work when both observer and target are specified - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc10 = SpectralCoord(500 * u.nm, - observer=ICRS(0 * u.deg, 0 * u.deg, distance=1 * u.m), - target=ICRS(10 * u.deg, 20 * u.deg, - radial_velocity=1 * u.km / u.s, - distance=10 * u.kpc)) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc10 = SpectralCoord( + 500 * u.nm, + observer=ICRS(0 * u.deg, 0 * u.deg, distance=1 * u.m), + target=ICRS( + 10 * u.deg, + 20 * u.deg, + radial_velocity=1 * u.km / u.s, + distance=10 * u.kpc, + ), + ) sc11 = sc10.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc11.radial_velocity, 2 * u.km / u.s) @@ -554,27 +701,44 @@ def test_los_shift_radial_velocity(): # Check that units are verified - with pytest.raises(u.UnitsError, match="Argument must have unit physical " - "type 'speed' for radial velocty or " - "'dimensionless' for redshift."): + with pytest.raises( + u.UnitsError, + match=( + "Argument must have unit physical type 'speed' for radial velocty or " + "'dimensionless' for redshift." + ), + ): sc1.with_radial_velocity_shift(target_shift=1 * u.kg) @pytest.mark.xfail def test_relativistic_radial_velocity(): - # Test for when both observer and target have relativistic velocities. # This is not yet supported, so the test is xfailed for now. - sc = SpectralCoord(500 * u.nm, - observer=ICRS(0 * u.km, 0 * u.km, 0 * u.km, - -0.5 * c, -0.5 * c, -0.5 * c, - representation_type='cartesian', - differential_type='cartesian'), - target=ICRS(1 * u.kpc, 1 * u.kpc, 1 * u.kpc, - 0.5 * c, 0.5 * c, 0.5 * c, - representation_type='cartesian', - differential_type='cartesian')) + sc = SpectralCoord( + 500 * u.nm, + observer=ICRS( + 0 * u.km, + 0 * u.km, + 0 * u.km, + -0.5 * c, + -0.5 * c, + -0.5 * c, + representation_type="cartesian", + differential_type="cartesian", + ), + target=ICRS( + 1 * u.kpc, + 1 * u.kpc, + 1 * u.kpc, + 0.5 * c, + 0.5 * c, + 0.5 * c, + representation_type="cartesian", + differential_type="cartesian", + ), + ) assert_quantity_allclose(sc.radial_velocity, 0.989743318610787 * u.km / u.s) @@ -586,11 +750,13 @@ def test_spectral_coord_jupiter(greenwich_earthlocation): """ Checks radial velocity between Earth and Jupiter """ - obstime = time.Time('2018-12-13 9:00') + obstime = time.Time("2018-12-13 9:00") obs = greenwich_earthlocation.get_gcrs(obstime) - pos, vel = get_body_barycentric_posvel('jupiter', obstime) - jupiter = SkyCoord(pos.with_differentials(CartesianDifferential(vel.xyz)), obstime=obstime) + pos, vel = get_body_barycentric_posvel("jupiter", obstime) + jupiter = SkyCoord( + pos.with_differentials(CartesianDifferential(vel.xyz)), obstime=obstime + ) spc = SpectralCoord([100, 200, 300] * u.nm, observer=obs, target=jupiter) @@ -605,13 +771,18 @@ def test_spectral_coord_alphacen(greenwich_earthlocation): """ Checks radial velocity between Earth and Alpha Centauri """ - obstime = time.Time('2018-12-13 9:00') + obstime = time.Time("2018-12-13 9:00") obs = greenwich_earthlocation.get_gcrs(obstime) # Coordinates were obtained from the following then hard-coded to avoid download # acen = SkyCoord.from_name('alpha cen') - acen = SkyCoord(ra=219.90085*u.deg, dec=-60.83562*u.deg, frame='icrs', - distance=4.37*u.lightyear, radial_velocity=-18.*u.km/u.s) + acen = SkyCoord( + ra=219.90085 * u.deg, + dec=-60.83562 * u.deg, + frame="icrs", + distance=4.37 * u.lightyear, + radial_velocity=-18.0 * u.km / u.s, + ) spc = SpectralCoord([100, 200, 300] * u.nm, observer=obs, target=acen) @@ -626,13 +797,17 @@ def test_spectral_coord_m31(greenwich_earthlocation): """ Checks radial velocity between Earth and M31 """ - obstime = time.Time('2018-12-13 9:00') + obstime = time.Time("2018-12-13 9:00") obs = greenwich_earthlocation.get_gcrs(obstime) # Coordinates were obtained from the following then hard-coded to avoid download # m31 = SkyCoord.from_name('M31') - m31 = SkyCoord(ra=10.6847*u.deg, dec=41.269*u.deg, - distance=710*u.kpc, radial_velocity=-300*u.km/u.s) + m31 = SkyCoord( + ra=10.6847 * u.deg, + dec=41.269 * u.deg, + distance=710 * u.kpc, + radial_velocity=-300 * u.km / u.s, + ) spc = SpectralCoord([100, 200, 300] * u.nm, observer=obs, target=m31) @@ -650,9 +825,9 @@ def test_shift_to_rest_galaxy(): doing basic rest-to-observed-and-back transformations """ z = 5 - rest_line_wls = [5007, 6563]*u.AA + rest_line_wls = [5007, 6563] * u.AA - observed_spc = SpectralCoord(rest_line_wls*(z+1), redshift=z) + observed_spc = SpectralCoord(rest_line_wls * (z + 1), redshift=z) rest_spc = observed_spc.to_rest() # alternatively: # rest_spc = observed_spc.with_observer(observed_spec.target) @@ -668,37 +843,45 @@ def test_shift_to_rest_galaxy(): def test_shift_to_rest_star_withobserver(greenwich_earthlocation): - rv = -8.3283011*u.km/u.s - rest_line_wls = [5007, 6563]*u.AA + rv = -8.3283011 * u.km / u.s + rest_line_wls = [5007, 6563] * u.AA - obstime = time.Time('2018-12-13 9:00') + obstime = time.Time("2018-12-13 9:00") eloc = greenwich_earthlocation obs = eloc.get_gcrs(obstime) - acen = SkyCoord(ra=219.90085*u.deg, dec=-60.83562*u.deg, frame='icrs', - distance=4.37*u.lightyear) + acen = SkyCoord( + ra=219.90085 * u.deg, + dec=-60.83562 * u.deg, + frame="icrs", + distance=4.37 * u.lightyear, + ) # Note that above the rv is missing from the SkyCoord. # That's intended, as it will instead be set in the `SpectralCoord`. But # the SpectralCoord machinery should yield something comparable to test_ # spectral_coord_alphacen - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - observed_spc = SpectralCoord(rest_line_wls*(rv/c + 1), - observer=obs, target=acen) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + observed_spc = SpectralCoord( + rest_line_wls * (rv / c + 1), observer=obs, target=acen + ) rest_spc = observed_spc.to_rest() assert_quantity_allclose(rest_spc, rest_line_wls) - barycentric_spc = observed_spc.with_observer_stationary_relative_to('icrs') + barycentric_spc = observed_spc.with_observer_stationary_relative_to("icrs") baryrest_spc = barycentric_spc.to_rest() assert quantity_allclose(baryrest_spc, rest_line_wls) # now make sure the change the barycentric shift did is comparable to the # offset rv_correction produces # barytarg = SkyCoord(barycentric_spc.target.frame) # should be this but that doesn't work for unclear reasons - barytarg = SkyCoord(barycentric_spc.target.data.without_differentials(), - frame=barycentric_spc.target.realize_frame(None)) - vcorr = barytarg.radial_velocity_correction(kind='barycentric', - obstime=obstime, location=eloc) + barytarg = SkyCoord( + barycentric_spc.target.data.without_differentials(), + frame=barycentric_spc.target.realize_frame(None), + ) + vcorr = barytarg.radial_velocity_correction( + kind="barycentric", obstime=obstime, location=eloc + ) drv = baryrest_spc.radial_velocity - observed_spc.radial_velocity @@ -707,47 +890,56 @@ def test_shift_to_rest_star_withobserver(greenwich_earthlocation): # adjusted if we think that's too aggressive of a precision target for what # the machinery can handle # with pytest.raises(AssertionError): - assert_quantity_allclose(vcorr, drv, atol=10*u.m/u.s) + assert_quantity_allclose(vcorr, drv, atol=10 * u.m / u.s) -gcrs_origin = GCRS(CartesianRepresentation([0*u.km, 0*u.km, 0*u.km])) -gcrs_not_origin = GCRS(CartesianRepresentation([1*u.km, 0*u.km, 0*u.km])) +gcrs_origin = GCRS(CartesianRepresentation([0 * u.km, 0 * u.km, 0 * u.km])) +gcrs_not_origin = GCRS(CartesianRepresentation([1 * u.km, 0 * u.km, 0 * u.km])) -@pytest.mark.parametrize("sc_kwargs", [ - dict(radial_velocity=0*u.km/u.s), - dict(observer=gcrs_origin, radial_velocity=0*u.km/u.s), - dict(target=gcrs_origin, radial_velocity=0*u.km/u.s), - dict(observer=gcrs_origin, target=gcrs_not_origin)]) +@pytest.mark.parametrize( + "sc_kwargs", + [ + dict(radial_velocity=0 * u.km / u.s), + dict(observer=gcrs_origin, radial_velocity=0 * u.km / u.s), + dict(target=gcrs_origin, radial_velocity=0 * u.km / u.s), + dict(observer=gcrs_origin, target=gcrs_not_origin), + ], +) def test_los_shift(sc_kwargs): - wl = [4000, 5000]*u.AA - with nullcontext() if 'observer' not in sc_kwargs and 'target' not in sc_kwargs else pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): + wl = [4000, 5000] * u.AA + with nullcontext() if "observer" not in sc_kwargs and "target" not in sc_kwargs else pytest.warns( + AstropyUserWarning, match="No velocity defined on frame" + ): sc_init = SpectralCoord(wl, **sc_kwargs) # these should always work in *all* cases because it's unambiguous that # a target shift should behave this way - new_sc1 = sc_init.with_radial_velocity_shift(.1) - assert_quantity_allclose(new_sc1, wl*1.1) - new_sc2 = sc_init.with_radial_velocity_shift(.1*u.dimensionless_unscaled) # interpret at redshift + new_sc1 = sc_init.with_radial_velocity_shift(0.1) + assert_quantity_allclose(new_sc1, wl * 1.1) + # interpret at redshift + new_sc2 = sc_init.with_radial_velocity_shift(0.1 * u.dimensionless_unscaled) assert_quantity_allclose(new_sc1, new_sc2) - new_sc3 = sc_init.with_radial_velocity_shift(-100*u.km/u.s) - assert_quantity_allclose(new_sc3, wl*(1 + (-100*u.km/u.s / c))) + new_sc3 = sc_init.with_radial_velocity_shift(-100 * u.km / u.s) + assert_quantity_allclose(new_sc3, wl * (1 + (-100 * u.km / u.s / c))) # now try the cases where observer is specified as well/instead if sc_init.observer is None or sc_init.target is None: with pytest.raises(ValueError): # both must be specified if you're going to mess with observer - sc_init.with_radial_velocity_shift(observer_shift=.1) + sc_init.with_radial_velocity_shift(observer_shift=0.1) if sc_init.observer is not None and sc_init.target is not None: # redshifting the observer should *blushift* the LOS velocity since # its the observer-to-target vector that matters - new_sc4 = sc_init.with_radial_velocity_shift(observer_shift=.1) - assert_quantity_allclose(new_sc4, wl/1.1) + new_sc4 = sc_init.with_radial_velocity_shift(observer_shift=0.1) + assert_quantity_allclose(new_sc4, wl / 1.1) # an equal shift in both should produce no offset at all - new_sc5 = sc_init.with_radial_velocity_shift(target_shift=.1, observer_shift=.1) + new_sc5 = sc_init.with_radial_velocity_shift( + target_shift=0.1, observer_shift=0.1 + ) assert_quantity_allclose(new_sc5, wl) @@ -756,50 +948,50 @@ def test_asteroid_velocity_frame_shifts(): This test mocks up the use case of observing a spectrum of an asteroid at different times and from different observer locations. """ - time1 = time.Time('2018-12-13 9:00') - dt = 12*u.hour + time1 = time.Time("2018-12-13 9:00") + dt = 12 * u.hour time2 = time1 + dt # make the silly but simplifying assumption that the astroid is moving along # the x-axis of GCRS, and makes a 10 earth-radius closest approach - v_ast = [5, 0, 0]*u.km/u.s - x1 = -v_ast[0]*dt / 2 - x2 = v_ast[0]*dt / 2 - z = 10*u.Rearth + v_ast = [5, 0, 0] * u.km / u.s + x1 = -v_ast[0] * dt / 2 + x2 = v_ast[0] * dt / 2 + z = 10 * u.Rearth cdiff = CartesianDifferential(v_ast) - asteroid_loc1 = GCRS(CartesianRepresentation(x1.to(u.km), - 0*u.km, - z.to(u.km), - differentials=cdiff), - obstime=time1) - asteroid_loc2 = GCRS(CartesianRepresentation(x2.to(u.km), - 0*u.km, - z.to(u.km), - differentials=cdiff), - obstime=time2) + asteroid_loc1 = GCRS( + CartesianRepresentation(x1.to(u.km), 0 * u.km, z.to(u.km), differentials=cdiff), + obstime=time1, + ) + asteroid_loc2 = GCRS( + CartesianRepresentation(x2.to(u.km), 0 * u.km, z.to(u.km), differentials=cdiff), + obstime=time2, + ) # assume satellites that are essentially fixed in geostationary orbit on # opposite sides of the earth - observer1 = GCRS(CartesianRepresentation([0*u.km, 35000*u.km, 0*u.km]), - obstime=time1) - observer2 = GCRS(CartesianRepresentation([0*u.km, -35000*u.km, 0*u.km]), - obstime=time2) + observer1 = GCRS( + CartesianRepresentation([0 * u.km, 35000 * u.km, 0 * u.km]), obstime=time1 + ) + observer2 = GCRS( + CartesianRepresentation([0 * u.km, -35000 * u.km, 0 * u.km]), obstime=time2 + ) wls = np.linspace(4000, 7000, 100) * u.AA - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): spec_coord1 = SpectralCoord(wls, observer=observer1, target=asteroid_loc1) - assert spec_coord1.radial_velocity < 0*u.km/u.s - assert spec_coord1.radial_velocity > -5*u.km/u.s + assert spec_coord1.radial_velocity < 0 * u.km / u.s + assert spec_coord1.radial_velocity > -5 * u.km / u.s - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): spec_coord2 = SpectralCoord(wls, observer=observer2, target=asteroid_loc2) - assert spec_coord2.radial_velocity > 0*u.km/u.s - assert spec_coord2.radial_velocity < 5*u.km/u.s + assert spec_coord2.radial_velocity > 0 * u.km / u.s + assert spec_coord2.radial_velocity < 5 * u.km / u.s # now check the behavior of with_observer_stationary_relative_to: we shift each coord # into the velocity frame of its *own* target. That would then be a @@ -814,14 +1006,15 @@ def test_asteroid_velocity_frame_shifts(): assert np.all(target_sc2 < spec_coord2) # rv/redshift should be 0 since the observer and target velocities should # be the same - assert_quantity_allclose(target_sc2.radial_velocity, 0*u.km/u.s, - atol=1e-7 * u.km / u.s) + assert_quantity_allclose( + target_sc2.radial_velocity, 0 * u.km / u.s, atol=1e-7 * u.km / u.s + ) # check that the same holds for spec_coord1, but be more specific: it # should follow the standard redshift formula (which in this case yields # a blueshift, although the formula is the same as 1+z) target_sc1 = spec_coord1.with_observer_stationary_relative_to(spec_coord1.target) - assert_quantity_allclose(target_sc1, spec_coord1/(1+spec_coord1.redshift)) + assert_quantity_allclose(target_sc1, spec_coord1 / (1 + spec_coord1.redshift)) # TODO: Figure out what is meant by the below use case # ensure the "target-rest" use gives the same answer @@ -831,26 +1024,29 @@ def test_asteroid_velocity_frame_shifts(): def test_spectral_coord_from_sky_coord_without_distance(): # see https://github.com/astropy/specutils/issues/658 for issue context - obs = SkyCoord(0 * u.m, 0 * u.m, 0 * u.m, representation_type='cartesian') - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): + obs = SkyCoord(0 * u.m, 0 * u.m, 0 * u.m, representation_type="cartesian") + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): coord = SpectralCoord([1, 2, 3] * u.micron, observer=obs) # coord.target = SkyCoord.from_name('m31') # <- original issue, but below is the same but requires no remote data access - with pytest.warns(AstropyUserWarning, match='Distance on coordinate object is dimensionless'): - coord.target = SkyCoord(ra=10.68470833*u.deg, dec=41.26875*u.deg) + with pytest.warns( + AstropyUserWarning, match="Distance on coordinate object is dimensionless" + ): + coord.target = SkyCoord(ra=10.68470833 * u.deg, dec=41.26875 * u.deg) -EXPECTED_VELOCITY_FRAMES = {'geocent': 'gcrs', - 'heliocent': 'hcrs', - 'lsrk': 'lsrk', - 'lsrd': 'lsrd', - 'galactoc': FITSWCS_VELOCITY_FRAMES['GALACTOC'], - 'localgrp': FITSWCS_VELOCITY_FRAMES['LOCALGRP']} +EXPECTED_VELOCITY_FRAMES = { + "geocent": "gcrs", + "heliocent": "hcrs", + "lsrk": "lsrk", + "lsrd": "lsrd", + "galactoc": FITSWCS_VELOCITY_FRAMES["GALACTOC"], + "localgrp": FITSWCS_VELOCITY_FRAMES["LOCALGRP"], +} -@pytest.mark.parametrize('specsys', list(EXPECTED_VELOCITY_FRAMES)) +@pytest.mark.parametrize("specsys", list(EXPECTED_VELOCITY_FRAMES)) @pytest.mark.slow def test_spectralcoord_accuracy(specsys): - # This is a test to check the numerical results of transformations between # different velocity frames in SpectralCoord. This compares the velocity # shifts determined with SpectralCoord to those determined from the rv @@ -858,33 +1054,47 @@ def test_spectralcoord_accuracy(specsys): velocity_frame = EXPECTED_VELOCITY_FRAMES[specsys] - reference_filename = get_pkg_data_filename('accuracy/data/rv.ecsv') - reference_table = Table.read(reference_filename, format='ascii.ecsv') + reference_filename = get_pkg_data_filename("accuracy/data/rv.ecsv") + reference_table = Table.read(reference_filename, format="ascii.ecsv") rest = 550 * u.nm - with iers.conf.set_temp('auto_download', False): - + with iers.conf.set_temp("auto_download", False): for row in reference_table: + observer = EarthLocation.from_geodetic( + -row["obslon"], row["obslat"] + ).get_itrs(obstime=row["obstime"]) - observer = EarthLocation.from_geodetic(-row['obslon'], row['obslat']).get_itrs(obstime=row['obstime']) - - with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): - sc_topo = SpectralCoord(545 * u.nm, observer=observer, target=row['target']) + with pytest.warns(AstropyUserWarning, match="No velocity defined on frame"): + sc_topo = SpectralCoord( + 545 * u.nm, observer=observer, target=row["target"] + ) # FIXME: A warning is emitted for dates after MJD=57754.0 even # though the leap second table should be valid until the end of # 2020. - with nullcontext() if row['obstime'].mjd < 57754 else pytest.warns(AstropyWarning, match='Tried to get polar motions'): + with nullcontext() if row["obstime"].mjd < 57754 else pytest.warns( + AstropyWarning, match="Tried to get polar motions" + ): sc_final = sc_topo.with_observer_stationary_relative_to(velocity_frame) - delta_vel = (sc_topo.to(u.km / u.s, doppler_convention='relativistic', doppler_rest=rest) - - sc_final.to(u.km / u.s, doppler_convention='relativistic', doppler_rest=rest)) + delta_vel = sc_topo.to( + u.km / u.s, doppler_convention="relativistic", doppler_rest=rest + ) - sc_final.to( + u.km / u.s, doppler_convention="relativistic", doppler_rest=rest + ) - if specsys == 'galactoc': - assert_allclose(delta_vel.to_value(u.km / u.s), row[specsys.lower()], atol=30) + if specsys == "galactoc": + assert_allclose( + delta_vel.to_value(u.km / u.s), row[specsys.lower()], atol=30 + ) else: - assert_allclose(delta_vel.to_value(u.km / u.s), row[specsys.lower()], atol=0.02, rtol=0.002) + assert_allclose( + delta_vel.to_value(u.km / u.s), + row[specsys.lower()], + atol=0.02, + rtol=0.002, + ) # TODO: add test when target is not ICRS diff --git a/astropy/coordinates/tests/test_spectral_quantity.py b/astropy/coordinates/tests/test_spectral_quantity.py index 901f13c4e19..5646faf14b9 100644 --- a/astropy/coordinates/tests/test_spectral_quantity.py +++ b/astropy/coordinates/tests/test_spectral_quantity.py @@ -10,27 +10,30 @@ class TestSpectralQuantity: - - @pytest.mark.parametrize('unit', SPECTRAL_UNITS) + @pytest.mark.parametrize("unit", SPECTRAL_UNITS) def test_init_value(self, unit): SpectralQuantity(1, unit=unit) - @pytest.mark.parametrize('unit', SPECTRAL_UNITS) + @pytest.mark.parametrize("unit", SPECTRAL_UNITS) def test_init_quantity(self, unit): SpectralQuantity(1 * unit) - @pytest.mark.parametrize('unit', SPECTRAL_UNITS) + @pytest.mark.parametrize("unit", SPECTRAL_UNITS) def test_init_spectralquantity(self, unit): SpectralQuantity(SpectralQuantity(1, unit=unit)) - @pytest.mark.parametrize('unit', (u.kg, u.byte)) + @pytest.mark.parametrize("unit", (u.kg, u.byte)) def test_init_invalid(self, unit): - with pytest.raises(u.UnitsError, match='SpectralQuantity instances require units'): + with pytest.raises( + u.UnitsError, match="SpectralQuantity instances require units" + ): SpectralQuantity(1, unit=unit) - with pytest.raises(u.UnitsError, match='SpectralQuantity instances require units'): + with pytest.raises( + u.UnitsError, match="SpectralQuantity instances require units" + ): SpectralQuantity(1 * unit) - @pytest.mark.parametrize(('unit1', 'unit2'), zip(SPECTRAL_UNITS, SPECTRAL_UNITS)) + @pytest.mark.parametrize(("unit1", "unit2"), zip(SPECTRAL_UNITS, SPECTRAL_UNITS)) def test_spectral_conversion(self, unit1, unit2): sq1 = SpectralQuantity(1 * unit1) sq2 = sq1.to(unit2) @@ -40,16 +43,17 @@ def test_spectral_conversion(self, unit1, unit2): assert_quantity_allclose(sq1, sq3) def test_doppler_conversion(self): - - sq1 = SpectralQuantity(1 * u.km / u.s, doppler_convention='optical', doppler_rest=500 * u.nm) + sq1 = SpectralQuantity( + 1 * u.km / u.s, doppler_convention="optical", doppler_rest=500 * u.nm + ) sq2 = sq1.to(u.m / u.s) assert_allclose(sq2.value, 1000) - sq3 = sq1.to(u.m / u.s, doppler_convention='radio') + sq3 = sq1.to(u.m / u.s, doppler_convention="radio") assert_allclose(sq3.value, 999.996664) - sq4 = sq1.to(u.m / u.s, doppler_convention='relativistic') + sq4 = sq1.to(u.m / u.s, doppler_convention="relativistic") assert_allclose(sq4.value, 999.998332) sq5 = sq1.to(u.m / u.s, doppler_rest=499.9 * u.nm) @@ -59,72 +63,103 @@ def test_doppler_conversion(self): assert_allclose(val5, 60970.685737) def test_doppler_conversion_validation(self): - sq1 = SpectralQuantity(1 * u.GHz) sq2 = SpectralQuantity(1 * u.km / u.s) - with pytest.raises(ValueError, match='doppler_convention not set, cannot convert to/from velocities'): + with pytest.raises( + ValueError, + match="doppler_convention not set, cannot convert to/from velocities", + ): sq1.to(u.km / u.s) - with pytest.raises(ValueError, match='doppler_convention not set, cannot convert to/from velocities'): + with pytest.raises( + ValueError, + match="doppler_convention not set, cannot convert to/from velocities", + ): sq2.to(u.GHz) - with pytest.raises(ValueError, match='doppler_rest not set, cannot convert to/from velocities'): - sq1.to(u.km / u.s, doppler_convention='radio') - - with pytest.raises(ValueError, match='doppler_rest not set, cannot convert to/from velocities'): - sq2.to(u.GHz, doppler_convention='radio') - - with pytest.raises(u.UnitsError, match="Argument 'doppler_rest' to function 'to' must be in units"): - sq1.to(u.km / u.s, doppler_convention='radio', doppler_rest=5 * u.kg) - - with pytest.raises(u.UnitsError, match="Argument 'doppler_rest' to function 'to' must be in units"): - sq2.to(u.GHz, doppler_convention='radio', doppler_rest=5 * u.kg) - - with pytest.raises(ValueError, match="doppler_convention should be one of optical/radio/relativistic"): - sq1.to(u.km / u.s, doppler_convention='banana', doppler_rest=5 * u.GHz) - - with pytest.raises(ValueError, match="doppler_convention should be one of optical/radio/relativistic"): - sq2.to(u.GHz, doppler_convention='banana', doppler_rest=5 * u.GHz) - - with pytest.raises(ValueError, match='Original doppler_convention not set'): - sq2.to(u.km / u.s, doppler_convention='radio') - - with pytest.raises(ValueError, match='Original doppler_rest not set'): + with pytest.raises( + ValueError, match="doppler_rest not set, cannot convert to/from velocities" + ): + sq1.to(u.km / u.s, doppler_convention="radio") + + with pytest.raises( + ValueError, match="doppler_rest not set, cannot convert to/from velocities" + ): + sq2.to(u.GHz, doppler_convention="radio") + + with pytest.raises( + u.UnitsError, + match="Argument 'doppler_rest' to function 'to' must be in units", + ): + sq1.to(u.km / u.s, doppler_convention="radio", doppler_rest=5 * u.kg) + + with pytest.raises( + u.UnitsError, + match="Argument 'doppler_rest' to function 'to' must be in units", + ): + sq2.to(u.GHz, doppler_convention="radio", doppler_rest=5 * u.kg) + + with pytest.raises( + ValueError, + match="doppler_convention should be one of optical/radio/relativistic", + ): + sq1.to(u.km / u.s, doppler_convention="banana", doppler_rest=5 * u.GHz) + + with pytest.raises( + ValueError, + match="doppler_convention should be one of optical/radio/relativistic", + ): + sq2.to(u.GHz, doppler_convention="banana", doppler_rest=5 * u.GHz) + + with pytest.raises(ValueError, match="Original doppler_convention not set"): + sq2.to(u.km / u.s, doppler_convention="radio") + + with pytest.raises(ValueError, match="Original doppler_rest not set"): sq2.to(u.km / u.s, doppler_rest=5 * u.GHz) def test_doppler_set_parameters(self): - sq1 = SpectralQuantity(1 * u.km / u.s) - with pytest.raises(ValueError, match="doppler_convention should be one of optical/radio/relativistic"): - sq1.doppler_convention = 'banana' + with pytest.raises( + ValueError, + match="doppler_convention should be one of optical/radio/relativistic", + ): + sq1.doppler_convention = "banana" assert sq1.doppler_convention is None - sq1.doppler_convention = 'radio' + sq1.doppler_convention = "radio" - assert sq1.doppler_convention == 'radio' + assert sq1.doppler_convention == "radio" - with pytest.raises(AttributeError, match="doppler_convention has already been set, and cannot be changed"): - sq1.doppler_convention = 'optical' + with pytest.raises( + AttributeError, + match="doppler_convention has already been set, and cannot be changed", + ): + sq1.doppler_convention = "optical" - assert sq1.doppler_convention == 'radio' + assert sq1.doppler_convention == "radio" - with pytest.raises(u.UnitsError, match="Argument 'value' to function 'doppler_rest' must be in units"): + with pytest.raises( + u.UnitsError, + match="Argument 'value' to function 'doppler_rest' must be in units", + ): sq1.doppler_rest = 5 * u.kg sq1.doppler_rest = 5 * u.GHz assert_quantity_allclose(sq1.doppler_rest, 5 * u.GHz) - with pytest.raises(AttributeError, match="doppler_rest has already been set, and cannot be changed"): + with pytest.raises( + AttributeError, + match="doppler_rest has already been set, and cannot be changed", + ): sq1.doppler_rest = 4 * u.GHz assert_quantity_allclose(sq1.doppler_rest, 5 * u.GHz) def test_arithmetic(self): - # Checks for arithmetic - some operations should return SpectralQuantity, # while some should just return plain Quantity @@ -154,7 +189,10 @@ def test_arithmetic(self): assert sq4.unit == u.AA sq5 = SpectralQuantity(10 * u.AA) - with pytest.raises(TypeError, match='Cannot store the result of this operation in SpectralQuantity'): + with pytest.raises( + TypeError, + match="Cannot store the result of this operation in SpectralQuantity", + ): sq5 += 10 * u.AA # Note different order to sq2 @@ -187,7 +225,6 @@ def test_arithmetic(self): assert q4.unit == u.one def test_ufuncs(self): - # Checks for ufuncs - some operations should return SpectralQuantity, # while some should just return plain Quantity @@ -201,7 +238,6 @@ def test_ufuncs(self): assert sq2.unit == u.AA def test_functions(self): - # Checks for other functions - some operations should return SpectralQuantity, # while some should just return plain Quantity @@ -224,7 +260,6 @@ def test_functions(self): @pytest.mark.xfail def test_functions_std(self): - # np.std should return a Quantity but it returns a SpectralQuantity. We # make this a separate xfailed test for now, but once this passes, # np.std could also just be added to the main test_functions test. diff --git a/astropy/coordinates/tests/test_transformations.py b/astropy/coordinates/tests/test_transformations.py index 9b348126810..8bbb0485a6a 100644 --- a/astropy/coordinates/tests/test_transformations.py +++ b/astropy/coordinates/tests/test_transformations.py @@ -41,25 +41,24 @@ def test_transform_classes(): """ Tests the class-based/OO syntax for creating transforms """ + def tfun(c, f): return f.__class__(ra=c.ra, dec=c.dec) - _ = t.FunctionTransform(tfun, TCoo1, TCoo2, - register_graph=frame_transform_graph) + _ = t.FunctionTransform(tfun, TCoo1, TCoo2, register_graph=frame_transform_graph) - c1 = TCoo1(ra=1*u.radian, dec=0.5*u.radian) + c1 = TCoo1(ra=1 * u.radian, dec=0.5 * u.radian) c2 = c1.transform_to(TCoo2()) assert_allclose(c2.ra.radian, 1) assert_allclose(c2.dec.radian, 0.5) def matfunc(coo, fr): - return [[1, 0, 0], - [0, coo.ra.degree, 0], - [0, 0, 1]] + return [[1, 0, 0], [0, coo.ra.degree, 0], [0, 0, 1]] + trans2 = t.DynamicMatrixTransform(matfunc, TCoo1, TCoo2) trans2.register(frame_transform_graph) - c3 = TCoo1(ra=1*u.deg, dec=2*u.deg) + c3 = TCoo1(ra=1 * u.deg, dec=2 * u.deg) c4 = c3.transform_to(TCoo2()) assert_allclose(c4.ra.degree, 1) @@ -74,7 +73,7 @@ def test_transform_decos(): """ Tests the decorator syntax for creating transforms """ - c1 = TCoo1(ra=1*u.deg, dec=2*u.deg) + c1 = TCoo1(ra=1 * u.deg, dec=2 * u.deg) @frame_transform_graph.transform(t.FunctionTransform, TCoo1, TCoo2) def trans(coo1, f): @@ -84,19 +83,17 @@ def trans(coo1, f): assert_allclose(c2.ra.degree, 1) assert_allclose(c2.dec.degree, 4) - c3 = TCoo1(r.CartesianRepresentation(x=1*u.pc, y=1*u.pc, z=2*u.pc)) + c3 = TCoo1(r.CartesianRepresentation(x=1 * u.pc, y=1 * u.pc, z=2 * u.pc)) @frame_transform_graph.transform(t.StaticMatrixTransform, TCoo1, TCoo2) def matrix(): - return [[2, 0, 0], - [0, 1, 0], - [0, 0, 1]] + return [[2, 0, 0], [0, 1, 0], [0, 0, 1]] c4 = c3.transform_to(TCoo2()) - assert_allclose(c4.cartesian.x, 2*u.pc) - assert_allclose(c4.cartesian.y, 1*u.pc) - assert_allclose(c4.cartesian.z, 2*u.pc) + assert_allclose(c4.cartesian.x, 2 * u.pc) + assert_allclose(c4.cartesian.y, 1 * u.pc) + assert_allclose(c4.cartesian.z, 2 * u.pc) def test_shortest_path(): @@ -129,14 +126,14 @@ def __init__(self, pri): assert path == [1, 3] assert d == 1 path, d = g.find_shortest_path(1, 4) - print('Cached paths:', g._shortestpaths) + print("Cached paths:", g._shortestpaths) assert path == [1, 2, 4] assert d == 2 # unreachable path, d = g.find_shortest_path(1, 5) assert path is None - assert d == float('inf') + assert d == float("inf") path, d = g.find_shortest_path(5, 6) assert path == [5, 6] @@ -160,7 +157,7 @@ def test_sphere_cart(): assert_allclose(y, 0) assert_allclose(z, 0) - x, y, z = spherical_to_cartesian(5, 0, np.arcsin(4. / 5.)) + x, y, z = spherical_to_cartesian(5, 0, np.arcsin(4.0 / 5.0)) assert_allclose(x, 3) assert_allclose(y, 4) assert_allclose(z, 0) @@ -204,11 +201,11 @@ def test_obstime(): Checks to make sure observation time is accounted for at least in FK4 <-> ICRS transformations """ - b1950 = Time('B1950') - j1975 = Time('J1975') + b1950 = Time("B1950") + j1975 = Time("J1975") - fk4_50 = FK4(ra=1*u.deg, dec=2*u.deg, obstime=b1950) - fk4_75 = FK4(ra=1*u.deg, dec=2*u.deg, obstime=j1975) + fk4_50 = FK4(ra=1 * u.deg, dec=2 * u.deg, obstime=b1950) + fk4_75 = FK4(ra=1 * u.deg, dec=2 * u.deg, obstime=j1975) icrs_50 = fk4_50.transform_to(ICRS()) icrs_75 = fk4_75.transform_to(ICRS()) @@ -218,6 +215,7 @@ def test_obstime(): assert icrs_50.ra.degree != icrs_75.ra.degree assert icrs_50.dec.degree != icrs_75.dec.degree + # ------------------------------------------------------------------------------ # Affine transform tests and helpers: @@ -225,24 +223,20 @@ def test_obstime(): class transfunc: - rep = r.CartesianRepresentation(np.arange(3)*u.pc) - dif = r.CartesianDifferential(*np.arange(3, 6)*u.pc/u.Myr) - rep0 = r.CartesianRepresentation(np.zeros(3)*u.pc) + rep = r.CartesianRepresentation(np.arange(3) * u.pc) + dif = r.CartesianDifferential(*np.arange(3, 6) * u.pc / u.Myr) + rep0 = r.CartesianRepresentation(np.zeros(3) * u.pc) @classmethod def both(cls, coo, fr): # exchange x <-> z and offset - M = np.array([[0., 0., 1.], - [0., 1., 0.], - [1., 0., 0.]]) + M = np.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) return M, cls.rep.with_differentials(cls.dif) @classmethod def just_matrix(cls, coo, fr): # exchange x <-> z and offset - M = np.array([[0., 0., 1.], - [0., 1., 0.], - [1., 0., 0.]]) + M = np.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) return M, None @classmethod @@ -258,9 +252,17 @@ def no_vel(cls, coo, fr): return None, cls.rep -@pytest.mark.parametrize('transfunc', [transfunc.both, transfunc.no_matrix, - transfunc.no_pos, transfunc.no_vel, - transfunc.just_matrix]) +@pytest.mark.parametrize( + "transfunc", + [ + transfunc.both, + transfunc.no_matrix, + transfunc.no_pos, + transfunc.no_vel, + transfunc.just_matrix, + ], +) +# fmt: off @pytest.mark.parametrize('rep', [ r.CartesianRepresentation(5, 6, 7, unit=u.pc), r.CartesianRepresentation(5, 6, 7, unit=u.pc, @@ -271,6 +273,7 @@ def no_vel(cls, coo, fr): unit=u.pc/u.Myr)) .represent_as(r.CylindricalRepresentation, r.CylindricalDifferential) ]) +# fmt: on def test_affine_transform_succeed(transfunc, rep): c = TCoo1(rep) @@ -278,8 +281,10 @@ def test_affine_transform_succeed(transfunc, rep): M, offset = transfunc(c, TCoo2) _rep = rep.to_cartesian() - diffs = {k: diff.represent_as(r.CartesianDifferential, rep) - for k, diff in rep.differentials.items()} + diffs = { + k: diff.represent_as(r.CartesianDifferential, rep) + for k, diff in rep.differentials.items() + } expected_rep = _rep.with_differentials(diffs) if M is not None: @@ -291,10 +296,10 @@ def test_affine_transform_succeed(transfunc, rep): expected_vel = None if c.data.differentials: - expected_vel = expected_rep.differentials['s'] + expected_vel = expected_rep.differentials["s"] if offset and offset.differentials: - expected_vel = (expected_vel + offset.differentials['s']) + expected_vel = expected_vel + offset.differentials["s"] # register and do the transformation and check against expected trans = t.AffineTransform(transfunc, TCoo1, TCoo2) @@ -302,11 +307,12 @@ def test_affine_transform_succeed(transfunc, rep): c2 = c.transform_to(TCoo2()) - assert quantity_allclose(c2.data.to_cartesian().xyz, - expected_pos.to_cartesian().xyz) + assert quantity_allclose( + c2.data.to_cartesian().xyz, expected_pos.to_cartesian().xyz + ) if expected_vel is not None: - diff = c2.data.differentials['s'].to_cartesian(base=c2.data) + diff = c2.data.differentials["s"].to_cartesian(base=c2.data) assert quantity_allclose(diff.xyz, expected_vel.d_xyz) trans.unregister(frame_transform_graph) @@ -316,12 +322,13 @@ def test_affine_transform_succeed(transfunc, rep): def transfunc_invalid_matrix(coo, fr): return np.eye(4), None + # Leaving this open in case we want to add more functions to check for failures -@pytest.mark.parametrize('transfunc', [transfunc_invalid_matrix]) +@pytest.mark.parametrize("transfunc", [transfunc_invalid_matrix]) def test_affine_transform_fail(transfunc): - diff = r.CartesianDifferential(8, 9, 10, unit=u.pc/u.Myr) + diff = r.CartesianDifferential(8, 9, 10, unit=u.pc / u.Myr) rep = r.CartesianRepresentation(5, 6, 7, unit=u.pc, differentials=diff) c = TCoo1(rep) @@ -336,10 +343,11 @@ def test_affine_transform_fail(transfunc): def test_too_many_differentials(): - dif1 = r.CartesianDifferential(*np.arange(3, 6)*u.pc/u.Myr) - dif2 = r.CartesianDifferential(*np.arange(3, 6)*u.pc/u.Myr**2) - rep = r.CartesianRepresentation(np.arange(3)*u.pc, - differentials={'s': dif1, 's2': dif2}) + dif1 = r.CartesianDifferential(*np.arange(3, 6) * u.pc / u.Myr) + dif2 = r.CartesianDifferential(*np.arange(3, 6) * u.pc / u.Myr**2) + rep = r.CartesianRepresentation( + np.arange(3) * u.pc, differentials={"s": dif1, "s2": dif2} + ) with pytest.raises(ValueError): c = TCoo1(rep) @@ -351,15 +359,17 @@ def test_too_many_differentials(): # Check that if frame somehow gets through to transformation, multiple # differentials are caught c = TCoo1(rep.without_differentials()) - c._data = c._data.with_differentials({'s': dif1, 's2': dif2}) + c._data = c._data.with_differentials({"s": dif1, "s2": dif2}) with pytest.raises(ValueError): c.transform_to(TCoo2()) trans.unregister(frame_transform_graph) + # A matrix transform of a unit spherical with differentials should work +# fmt: off @pytest.mark.parametrize('rep', [ r.UnitSphericalRepresentation(lon=15*u.degree, lat=-11*u.degree, differentials=r.SphericalDifferential(d_lon=15*u.mas/u.yr, @@ -371,8 +381,8 @@ def test_too_many_differentials(): distance=150*u.pc, differentials={'s': r.RadialDifferential(d_distance=-110*u.km/u.s)}) ]) +# fmt: on def test_unit_spherical_with_differentials(rep): - c = TCoo1(rep) # register and do the transformation and check against expected @@ -380,12 +390,11 @@ def test_unit_spherical_with_differentials(rep): trans.register(frame_transform_graph) c2 = c.transform_to(TCoo2()) - assert 's' in rep.differentials - assert isinstance(c2.data.differentials['s'], - rep.differentials['s'].__class__) + assert "s" in rep.differentials + assert isinstance(c2.data.differentials["s"], rep.differentials["s"].__class__) - if isinstance(rep.differentials['s'], r.RadialDifferential): - assert c2.data.differentials['s'] is rep.differentials['s'] + if isinstance(rep.differentials["s"], r.RadialDifferential): + assert c2.data.differentials["s"] is rep.differentials["s"] trans.unregister(frame_transform_graph) @@ -403,25 +412,25 @@ def test_vel_transformation_obstime_err(): # TODO: replace after a final decision on PR #6280 from astropy.coordinates.sites import get_builtin_sites - diff = r.CartesianDifferential([.1, .2, .3]*u.km/u.s) - rep = r.CartesianRepresentation([1, 2, 3]*u.au, differentials=diff) + diff = r.CartesianDifferential([0.1, 0.2, 0.3] * u.km / u.s) + rep = r.CartesianRepresentation([1, 2, 3] * u.au, differentials=diff) - loc = get_builtin_sites()['example_site'] + loc = get_builtin_sites()["example_site"] - aaf = AltAz(obstime='J2010', location=loc) - aaf2 = AltAz(obstime=aaf.obstime + 3*u.day, location=loc) - aaf3 = AltAz(obstime=aaf.obstime + np.arange(3)*u.day, location=loc) + aaf = AltAz(obstime="J2010", location=loc) + aaf2 = AltAz(obstime=aaf.obstime + 3 * u.day, location=loc) + aaf3 = AltAz(obstime=aaf.obstime + np.arange(3) * u.day, location=loc) aaf4 = AltAz(obstime=aaf.obstime, location=loc) aa = aaf.realize_frame(rep) with pytest.raises(NotImplementedError) as exc: aa.transform_to(aaf2) - assert 'cannot transform' in exc.value.args[0] + assert "cannot transform" in exc.value.args[0] with pytest.raises(NotImplementedError) as exc: aa.transform_to(aaf3) - assert 'cannot transform' in exc.value.args[0] + assert "cannot transform" in exc.value.args[0] aa.transform_to(aaf4) @@ -432,13 +441,16 @@ def test_function_transform_with_differentials(): def tfun(c, f): return f.__class__(ra=c.ra, dec=c.dec) - _ = t.FunctionTransform(tfun, TCoo3, TCoo2, - register_graph=frame_transform_graph) + _ = t.FunctionTransform(tfun, TCoo3, TCoo2, register_graph=frame_transform_graph) - t3 = TCoo3(ra=1*u.deg, dec=2*u.deg, pm_ra_cosdec=1*u.marcsec/u.yr, - pm_dec=1*u.marcsec/u.yr,) + t3 = TCoo3( + ra=1 * u.deg, + dec=2 * u.deg, + pm_ra_cosdec=1 * u.marcsec / u.yr, + pm_dec=1 * u.marcsec / u.yr, + ) - with pytest.warns(AstropyWarning, match=r'.*they have been dropped.*') as w: + with pytest.warns(AstropyWarning, match=r".*they have been dropped.*") as w: t3.transform_to(TCoo2()) assert len(w) == 1 @@ -462,9 +474,11 @@ def trans_func(coo1, f): with pytest.raises(ValueError) as exc: trans.register(frame_transform_graph) - assert ('BorkedFrame' in exc.value.args[0] and - "'ra'" in exc.value.args[0] and - "'dec'" in exc.value.args[0]) + assert ( + "BorkedFrame" in exc.value.args[0] + and "'ra'" in exc.value.args[0] + and "'dec'" in exc.value.args[0] + ) def test_static_matrix_combine_paths(): @@ -481,25 +495,21 @@ class AFrame(BaseCoordinateFrame): default_representation = r.SphericalRepresentation default_differential = r.SphericalCosLatDifferential - t1 = t.StaticMatrixTransform(rotation_matrix(30.*u.deg, 'z'), - ICRS, AFrame) + t1 = t.StaticMatrixTransform(rotation_matrix(30.0 * u.deg, "z"), ICRS, AFrame) t1.register(frame_transform_graph) - t2 = t.StaticMatrixTransform(rotation_matrix(30.*u.deg, 'z').T, - AFrame, ICRS) + t2 = t.StaticMatrixTransform(rotation_matrix(30.0 * u.deg, "z").T, AFrame, ICRS) t2.register(frame_transform_graph) class BFrame(BaseCoordinateFrame): default_representation = r.SphericalRepresentation default_differential = r.SphericalCosLatDifferential - t3 = t.StaticMatrixTransform(rotation_matrix(30.*u.deg, 'x'), - ICRS, BFrame) + t3 = t.StaticMatrixTransform(rotation_matrix(30.0 * u.deg, "x"), ICRS, BFrame) t3.register(frame_transform_graph) - t4 = t.StaticMatrixTransform(rotation_matrix(30.*u.deg, 'x').T, - BFrame, ICRS) + t4 = t.StaticMatrixTransform(rotation_matrix(30.0 * u.deg, "x").T, BFrame, ICRS) t4.register(frame_transform_graph) - c = Galactic(123*u.deg, 45*u.deg) + c = Galactic(123 * u.deg, 45 * u.deg) c1 = c.transform_to(BFrame()) # direct c2 = c.transform_to(AFrame()).transform_to(BFrame()) # thru A c3 = c.transform_to(ICRS()).transform_to(BFrame()) # thru ICRS @@ -519,7 +529,7 @@ def test_multiple_aliases(): # Define a frame with multiple aliases class MultipleAliasesFrame(BaseCoordinateFrame): - name = ['alias_1', 'alias_2'] + name = ["alias_1", "alias_2"] default_representation = r.SphericalRepresentation def tfun(c, f): @@ -527,16 +537,17 @@ def tfun(c, f): # Register a transform graph = t.TransformGraph() - _ = t.FunctionTransform(tfun, MultipleAliasesFrame, MultipleAliasesFrame, - register_graph=graph) + _ = t.FunctionTransform( + tfun, MultipleAliasesFrame, MultipleAliasesFrame, register_graph=graph + ) # Test that both aliases have been added to the transform graph - assert graph.lookup_name('alias_1') == MultipleAliasesFrame - assert graph.lookup_name('alias_2') == MultipleAliasesFrame + assert graph.lookup_name("alias_1") == MultipleAliasesFrame + assert graph.lookup_name("alias_2") == MultipleAliasesFrame # Test that both aliases appear in the graphviz DOT format output dotstr = graph.to_dot_graph() - assert '`alias_1`\\n`alias_2`' in dotstr + assert "`alias_1`\\n`alias_2`" in dotstr def test_remove_transform_and_unregister(): @@ -610,16 +621,19 @@ class H3(HCRS): tfun = lambda c, f: f.__class__(ra=c.ra, dec=c.dec) # Set up a number of transforms with different time steps - old_dt = 1*u.min - transform1 = t.FunctionTransformWithFiniteDifference(tfun, H1, H1, register_graph=graph, - finite_difference_dt=old_dt) - transform2 = t.FunctionTransformWithFiniteDifference(tfun, H2, H2, register_graph=graph, - finite_difference_dt=old_dt * 2) - transform3 = t.FunctionTransformWithFiniteDifference(tfun, H2, H3, register_graph=graph, - finite_difference_dt=old_dt * 3) + old_dt = 1 * u.min + transform1 = t.FunctionTransformWithFiniteDifference( + tfun, H1, H1, register_graph=graph, finite_difference_dt=old_dt + ) + transform2 = t.FunctionTransformWithFiniteDifference( + tfun, H2, H2, register_graph=graph, finite_difference_dt=old_dt * 2 + ) + transform3 = t.FunctionTransformWithFiniteDifference( + tfun, H2, H3, register_graph=graph, finite_difference_dt=old_dt * 3 + ) # Check that all of the transforms have the same new time step - new_dt = 1*u.yr + new_dt = 1 * u.yr with graph.impose_finite_difference_dt(new_dt): assert transform1.finite_difference_dt == new_dt assert transform2.finite_difference_dt == new_dt @@ -631,6 +645,7 @@ class H3(HCRS): assert transform3.finite_difference_dt == old_dt * 3 +# fmt: off @pytest.mark.parametrize("first, second, check", [((rotation_matrix(30*u.deg), None), (rotation_matrix(45*u.deg), None), @@ -662,6 +677,7 @@ class H3(HCRS): ((None, None), (None, None), (None, None))]) +# fmt: on def test_combine_affine_params(first, second, check): result = t._combine_affine_params(first, second) if check[0] is None: diff --git a/astropy/coordinates/tests/test_unit_representation.py b/astropy/coordinates/tests/test_unit_representation.py index c2acfdd2a9c..521b1b71418 100644 --- a/astropy/coordinates/tests/test_unit_representation.py +++ b/astropy/coordinates/tests/test_unit_representation.py @@ -32,41 +32,44 @@ def teardown_function(func): def test_unit_representation_subclass(): - class Longitude180(Longitude): - def __new__(cls, angle, unit=None, wrap_angle=180*u.deg, **kwargs): - self = super().__new__(cls, angle, unit=unit, wrap_angle=wrap_angle, - **kwargs) + def __new__(cls, angle, unit=None, wrap_angle=180 * u.deg, **kwargs): + self = super().__new__( + cls, angle, unit=unit, wrap_angle=wrap_angle, **kwargs + ) return self class UnitSphericalWrap180Representation(UnitSphericalRepresentation): - attr_classes = {'lon': Longitude180, - 'lat': Latitude} + attr_classes = {"lon": Longitude180, "lat": Latitude} class SphericalWrap180Representation(SphericalRepresentation): - attr_classes = {'lon': Longitude180, - 'lat': Latitude, - 'distance': u.Quantity} + attr_classes = {"lon": Longitude180, "lat": Latitude, "distance": u.Quantity} _unit_representation = UnitSphericalWrap180Representation class MyFrame(ICRS): default_representation = SphericalWrap180Representation frame_specific_representation_info = { - 'spherical': [ - RepresentationMapping('lon', 'ra'), - RepresentationMapping('lat', 'dec')] + "spherical": [ + RepresentationMapping("lon", "ra"), + RepresentationMapping("lat", "dec"), + ] } - frame_specific_representation_info['unitsphericalwrap180'] = \ - frame_specific_representation_info['sphericalwrap180'] = \ - frame_specific_representation_info['spherical'] - - @frame_transform_graph.transform(FunctionTransform, - MyFrame, astropy.coordinates.ICRS) + frame_specific_representation_info[ + "unitsphericalwrap180" + ] = frame_specific_representation_info[ + "sphericalwrap180" + ] = frame_specific_representation_info[ + "spherical" + ] + + @frame_transform_graph.transform( + FunctionTransform, MyFrame, astropy.coordinates.ICRS + ) def myframe_to_icrs(myframe_coo, icrs): return icrs.realize_frame(myframe_coo._data) - f = MyFrame(10*u.deg, 10*u.deg) + f = MyFrame(10 * u.deg, 10 * u.deg) assert isinstance(f._data, UnitSphericalWrap180Representation) assert isinstance(f.ra, Longitude180) @@ -74,6 +77,4 @@ def myframe_to_icrs(myframe_coo, icrs): assert isinstance(g, astropy.coordinates.ICRS) assert isinstance(g._data, UnitSphericalWrap180Representation) - frame_transform_graph.remove_transform(MyFrame, - astropy.coordinates.ICRS, - None) + frame_transform_graph.remove_transform(MyFrame, astropy.coordinates.ICRS, None) diff --git a/astropy/coordinates/tests/test_utils.py b/astropy/coordinates/tests/test_utils.py index 0c813f96d1c..087f24816fb 100644 --- a/astropy/coordinates/tests/test_utils.py +++ b/astropy/coordinates/tests/test_utils.py @@ -11,18 +11,18 @@ def test_polar_motion_unsupported_dates(): - msg = r'Tried to get polar motions for times {} IERS.*' + msg = r"Tried to get polar motions for times {} IERS.*" - with pytest.warns(AstropyWarning, match=msg.format('before')): - get_polar_motion(Time('1900-01-01')) + with pytest.warns(AstropyWarning, match=msg.format("before")): + get_polar_motion(Time("1900-01-01")) - with pytest.warns(AstropyWarning, match=msg.format('after')): - get_polar_motion(Time('2100-01-01')) + with pytest.warns(AstropyWarning, match=msg.format("after")): + get_polar_motion(Time("2100-01-01")) def test_sun_from_barycenter_offset(): - time = Time('2020-01-01') - pos, vel = get_body_barycentric_posvel('sun', time) + time = Time("2020-01-01") + pos, vel = get_body_barycentric_posvel("sun", time) offset = get_offset_sun_from_barycenter(time) assert_quantity_allclose(offset.xyz, pos.xyz) @@ -30,12 +30,14 @@ def test_sun_from_barycenter_offset(): offset_with_vel = get_offset_sun_from_barycenter(time, include_velocity=True) assert_quantity_allclose(offset_with_vel.xyz, pos.xyz) - assert_quantity_allclose(offset_with_vel.differentials['s'].d_xyz, vel.xyz) + assert_quantity_allclose(offset_with_vel.differentials["s"].d_xyz, vel.xyz) reverse = get_offset_sun_from_barycenter(time, reverse=True) assert_quantity_allclose(reverse.xyz, -pos.xyz) assert not bool(reverse.differentials) - reverse_with_vel = get_offset_sun_from_barycenter(time, reverse=True, include_velocity=True) + reverse_with_vel = get_offset_sun_from_barycenter( + time, reverse=True, include_velocity=True + ) assert_quantity_allclose(reverse_with_vel.xyz, -pos.xyz) - assert_quantity_allclose(reverse_with_vel.differentials['s'].d_xyz, -vel.xyz) + assert_quantity_allclose(reverse_with_vel.differentials["s"].d_xyz, -vel.xyz) diff --git a/astropy/coordinates/tests/test_velocity_corrs.py b/astropy/coordinates/tests/test_velocity_corrs.py index 313abe85cdc..69f2cf9f12e 100644 --- a/astropy/coordinates/tests/test_velocity_corrs.py +++ b/astropy/coordinates/tests/test_velocity_corrs.py @@ -11,30 +11,29 @@ from astropy.utils.data import get_pkg_data_filename -@pytest.mark.parametrize('kind', ['heliocentric', 'barycentric']) +@pytest.mark.parametrize("kind", ["heliocentric", "barycentric"]) def test_basic(kind): - t0 = Time('2015-1-1') - loc = get_builtin_sites()['example_site'] + t0 = Time("2015-1-1") + loc = get_builtin_sites()["example_site"] sc = SkyCoord(0, 0, unit=u.deg, obstime=t0, location=loc) rvc0 = sc.radial_velocity_correction(kind) assert rvc0.shape == () - assert rvc0.unit.is_equivalent(u.km/u.s) + assert rvc0.unit.is_equivalent(u.km / u.s) - scs = SkyCoord(0, 0, unit=u.deg, obstime=t0 + np.arange(10)*u.day, - location=loc) + scs = SkyCoord(0, 0, unit=u.deg, obstime=t0 + np.arange(10) * u.day, location=loc) rvcs = scs.radial_velocity_correction(kind) assert rvcs.shape == (10,) - assert rvcs.unit.is_equivalent(u.km/u.s) + assert rvcs.unit.is_equivalent(u.km / u.s) -test_input_time = Time(2457244.5, format='jd') +test_input_time = Time(2457244.5, format="jd") # test_input_loc = EarthLocation.of_site('Cerro Paranal') # to avoid the network hit we just copy here what that yields -test_input_loc = EarthLocation.from_geodetic(lon=-70.403*u.deg, - lat=-24.6252*u.deg, - height=2635*u.m) +test_input_loc = EarthLocation.from_geodetic( + lon=-70.403 * u.deg, lat=-24.6252 * u.deg, height=2635 * u.m +) def test_helio_iraf(): @@ -149,15 +148,16 @@ def test_helio_iraf(): 2457244.49805 0.00 6.84 16.77 -0.034 -0.001 6.874 9.935 """ vhs_iraf = [] - for line in rvcorr_result.strip().split('\n'): - if not line.strip().startswith('#'): + for line in rvcorr_result.strip().split("\n"): + if not line.strip().startswith("#"): vhs_iraf.append(float(line.split()[2])) - vhs_iraf = vhs_iraf*u.km/u.s + vhs_iraf = vhs_iraf * u.km / u.s - targets = SkyCoord(_get_test_input_radecs(), obstime=test_input_time, - location=test_input_loc) - vhs_astropy = targets.radial_velocity_correction('heliocentric') - assert_quantity_allclose(vhs_astropy, vhs_iraf, atol=150*u.m/u.s) + targets = SkyCoord( + _get_test_input_radecs(), obstime=test_input_time, location=test_input_loc + ) + vhs_astropy = targets.radial_velocity_correction("heliocentric") + assert_quantity_allclose(vhs_astropy, vhs_iraf, atol=150 * u.m / u.s) def generate_IRAF_input(writefn=None): @@ -167,21 +167,20 @@ def generate_IRAF_input(writefn=None): lines = [] for ra, dec in zip(coos.ra, coos.dec): - rastr = Angle(ra).to_string(u.hour, sep=':') - decstr = Angle(dec).to_string(u.deg, sep=':') + rastr = Angle(ra).to_string(u.hour, sep=":") + decstr = Angle(dec).to_string(u.deg, sep=":") - msg = '{yr} {mo} {day} {uth}:{utmin} {ra} {dec}' - lines.append(msg.format(yr=dt.year, mo=dt.month, day=dt.day, - uth=dt.hour, utmin=dt.minute, - ra=rastr, dec=decstr)) + lines.append( + f"{dt.year} {dt.month} {dt.day} {dt.hour}:{dt.minute} {rastr} {decstr}" + ) if writefn: - with open(writefn, 'w') as f: + with open(writefn, "w") as f: for l in lines: f.write(l) else: for l in lines: print(l) - print('Run IRAF as:\nastutil\nrvcorrect f= observatory=Paranal') + print("Run IRAF as:\nastutil\nrvcorrect f= observatory=Paranal") def _get_test_input_radecs(): @@ -189,16 +188,17 @@ def _get_test_input_radecs(): decs = [] for dec in np.linspace(-85, 85, 15): - nra = int(np.round(10*np.cos(dec*u.deg)).value) - ras1 = np.linspace(-180, 180-1e-6, nra) + nra = int(np.round(10 * np.cos(dec * u.deg)).value) + ras1 = np.linspace(-180, 180 - 1e-6, nra) ras.extend(ras1) - decs.extend([dec]*len(ras1)) + decs.extend([dec] * len(ras1)) return SkyCoord(ra=ras, dec=decs, unit=u.deg) def test_barycorr(): # this is the result of calling _get_barycorr_bvcs + # fmt: off barycorr_bvcs = u.Quantity([ -10335.93326096, -14198.47605491, -2237.60012494, -14198.47595363, -17425.46512587, -17131.70901174, 2424.37095076, 2130.61519166, @@ -225,15 +225,16 @@ def test_barycorr(): -2121.44599781, 17434.63406085, 17140.87871753, -2415.2018495, 2246.76923076, 14207.64513054, 2246.76933194, 6808.40787728], u.m/u.s) + # fmt: on # this tries the *other* way of calling radial_velocity_correction relative # to the IRAF tests targets = _get_test_input_radecs() - bvcs_astropy = targets.radial_velocity_correction(obstime=test_input_time, - location=test_input_loc, - kind='barycentric') + bvcs_astropy = targets.radial_velocity_correction( + obstime=test_input_time, location=test_input_loc, kind="barycentric" + ) - assert_quantity_allclose(bvcs_astropy, barycorr_bvcs, atol=10*u.mm/u.s) + assert_quantity_allclose(bvcs_astropy, barycorr_bvcs, atol=10 * u.mm / u.s) def _get_barycorr_bvcs(coos, loc, injupyter=False): @@ -251,38 +252,44 @@ def _get_barycorr_bvcs(coos, loc, injupyter=False): from astropy.utils.console import ProgressBar bvcs = [] - for ra, dec in ProgressBar(list(zip(coos.ra.deg, coos.dec.deg)), - ipython_widget=injupyter): - res = barycorr.bvc(test_input_time.utc.jd, ra, dec, - lat=loc.geodetic[1].deg, - lon=loc.geodetic[0].deg, - elevation=loc.geodetic[2].to(u.m).value) + for ra, dec in ProgressBar( + list(zip(coos.ra.deg, coos.dec.deg)), ipython_widget=injupyter + ): + res = barycorr.bvc( + test_input_time.utc.jd, + ra, + dec, + lat=loc.geodetic[1].deg, + lon=loc.geodetic[0].deg, + elevation=loc.geodetic[2].to(u.m).value, + ) bvcs.append(res) - return bvcs*u.m/u.s + return bvcs * u.m / u.s def test_rvcorr_multiple_obstimes_onskycoord(): loc = EarthLocation(-2309223 * u.m, -3695529 * u.m, -4641767 * u.m) - arrtime = Time('2005-03-21 00:00:00') + np.linspace(-1, 1, 10)*u.day + arrtime = Time("2005-03-21 00:00:00") + np.linspace(-1, 1, 10) * u.day - sc = SkyCoord(1*u.deg, 2*u.deg, 100*u.kpc, obstime=arrtime, location=loc) - rvcbary_sc2 = sc.radial_velocity_correction(kind='barycentric') + sc = SkyCoord(1 * u.deg, 2 * u.deg, 100 * u.kpc, obstime=arrtime, location=loc) + rvcbary_sc2 = sc.radial_velocity_correction(kind="barycentric") assert len(rvcbary_sc2) == 10 # check the multiple-obstime and multi- mode - sc = SkyCoord(([1]*10)*u.deg, 2*u.deg, 100*u.kpc, - obstime=arrtime, location=loc) - rvcbary_sc3 = sc.radial_velocity_correction(kind='barycentric') + sc = SkyCoord( + ([1] * 10) * u.deg, 2 * u.deg, 100 * u.kpc, obstime=arrtime, location=loc + ) + rvcbary_sc3 = sc.radial_velocity_correction(kind="barycentric") assert len(rvcbary_sc3) == 10 def test_invalid_argument_combos(): loc = EarthLocation(-2309223 * u.m, -3695529 * u.m, -4641767 * u.m) - time = Time('2005-03-21 00:00:00') - timel = Time('2005-03-21 00:00:00', location=loc) + time = Time("2005-03-21 00:00:00") + timel = Time("2005-03-21 00:00:00", location=loc) - scwattrs = SkyCoord(1*u.deg, 2*u.deg, obstime=time, location=loc) - scwoattrs = SkyCoord(1*u.deg, 2*u.deg) + scwattrs = SkyCoord(1 * u.deg, 2 * u.deg, obstime=time, location=loc) + scwoattrs = SkyCoord(1 * u.deg, 2 * u.deg) scwattrs.radial_velocity_correction() with pytest.raises(ValueError): @@ -299,16 +306,30 @@ def test_invalid_argument_combos(): def test_regression_9645(): - sc = SkyCoord(10*u.deg, 20*u.deg, distance=5*u.pc, obstime=test_input_time, - pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, radial_velocity=0*u.km/u.s) - sc_novel = SkyCoord(10*u.deg, 20*u.deg, distance=5*u.pc, obstime=test_input_time) - corr = sc.radial_velocity_correction(obstime=test_input_time, location=test_input_loc) - corr_novel = sc_novel.radial_velocity_correction(obstime=test_input_time, location=test_input_loc) + sc = SkyCoord( + 10 * u.deg, + 20 * u.deg, + distance=5 * u.pc, + obstime=test_input_time, + pm_ra_cosdec=0 * u.mas / u.yr, + pm_dec=0 * u.mas / u.yr, + radial_velocity=0 * u.km / u.s, + ) + sc_novel = SkyCoord( + 10 * u.deg, 20 * u.deg, distance=5 * u.pc, obstime=test_input_time + ) + corr = sc.radial_velocity_correction( + obstime=test_input_time, location=test_input_loc + ) + corr_novel = sc_novel.radial_velocity_correction( + obstime=test_input_time, location=test_input_loc + ) assert_quantity_allclose(corr, corr_novel) def test_barycorr_withvels(): # this is the result of calling _get_barycorr_bvcs_withvels + # fmt: off barycorr_bvcs = u.Quantity( [-10335.94926581, -14198.49117304, -2237.58656335, -14198.49078575, -17425.47883864, -17131.72711182, @@ -342,23 +363,31 @@ def test_barycorr_withvels(): -2415.17899385, -2121.44598968, 17434.60465075, 17140.87204017, -2415.1771038, 2246.79688215, 14207.61339552, 2246.79790276, 6808.43888253], u.m/u.s) + # fmt: on coos = _get_test_input_radecvels() - bvcs_astropy = coos.radial_velocity_correction(obstime=test_input_time, - location=test_input_loc) - assert_quantity_allclose(bvcs_astropy, barycorr_bvcs, atol=10*u.mm/u.s) + bvcs_astropy = coos.radial_velocity_correction( + obstime=test_input_time, location=test_input_loc + ) + assert_quantity_allclose(bvcs_astropy, barycorr_bvcs, atol=10 * u.mm / u.s) def _get_test_input_radecvels(): coos = _get_test_input_radecs() ras = coos.ra decs = coos.dec - pmra = np.linspace(-1000, 1000, coos.size)*u.mas/u.yr - pmdec = np.linspace(0, 1000, coos.size)*u.mas/u.yr - rvs = np.linspace(0, 100, coos.size)*u.km/u.s - distance = np.linspace(10, 100, coos.size)*u.pc - return SkyCoord(ras, decs, pm_ra_cosdec=pmra, pm_dec=pmdec, - radial_velocity=rvs, distance=distance, - obstime=test_input_time) + pmra = np.linspace(-1000, 1000, coos.size) * u.mas / u.yr + pmdec = np.linspace(0, 1000, coos.size) * u.mas / u.yr + rvs = np.linspace(0, 100, coos.size) * u.km / u.s + distance = np.linspace(10, 100, coos.size) * u.pc + return SkyCoord( + ras, + decs, + pm_ra_cosdec=pmra, + pm_dec=pmdec, + radial_velocity=rvs, + distance=distance, + obstime=test_input_time, + ) def _get_barycorr_bvcs_withvels(coos, loc, injupyter=False): @@ -377,27 +406,34 @@ def _get_barycorr_bvcs_withvels(coos, loc, injupyter=False): bvcs = [] for coo in ProgressBar(coos, ipython_widget=injupyter): - res = barycorr.bvc(test_input_time.utc.jd, - coo.ra.deg, coo.dec.deg, - lat=loc.geodetic[1].deg, - lon=loc.geodetic[0].deg, - pmra=coo.pm_ra_cosdec.to_value(u.mas/u.yr), - pmdec=coo.pm_dec.to_value(u.mas/u.yr), - parallax=coo.distance.to_value(u.mas, equivalencies=u.parallax()), - rv=coo.radial_velocity.to_value(u.m/u.s), - epoch=test_input_time.utc.jd, - elevation=loc.geodetic[2].to(u.m).value) + res = barycorr.bvc( + test_input_time.utc.jd, + coo.ra.deg, + coo.dec.deg, + lat=loc.geodetic[1].deg, + lon=loc.geodetic[0].deg, + pmra=coo.pm_ra_cosdec.to_value(u.mas / u.yr), + pmdec=coo.pm_dec.to_value(u.mas / u.yr), + parallax=coo.distance.to_value(u.mas, equivalencies=u.parallax()), + rv=coo.radial_velocity.to_value(u.m / u.s), + epoch=test_input_time.utc.jd, + elevation=loc.geodetic[2].to(u.m).value, + ) bvcs.append(res) - return bvcs*u.m/u.s + return bvcs * u.m / u.s def test_warning_no_obstime_on_skycoord(): - c = SkyCoord(l=10*u.degree, b=45*u.degree, - pm_l_cosb=34*u.mas/u.yr, pm_b=-117*u.mas/u.yr, - distance=50*u.pc, frame='galactic') + c = SkyCoord( + l=10 * u.degree, + b=45 * u.degree, + pm_l_cosb=34 * u.mas / u.yr, + pm_b=-117 * u.mas / u.yr, + distance=50 * u.pc, + frame="galactic", + ) with pytest.warns(Warning): - c.radial_velocity_correction('barycentric', test_input_time, - test_input_loc) + c.radial_velocity_correction("barycentric", test_input_time, test_input_loc) @pytest.mark.remote_data @@ -412,27 +448,29 @@ def test_regression_10094(): # Corrections for tau Ceti wright_table = Table.read( - get_pkg_data_filename('coordinates/wright_eastmann_2014_tau_ceti.fits') + get_pkg_data_filename("coordinates/wright_eastmann_2014_tau_ceti.fits") ) - reduced_jds = wright_table['JD-2400000'] - tempo2 = wright_table['TEMPO2'] - barycorr = wright_table['BARYCORR'] + reduced_jds = wright_table["JD-2400000"] + tempo2 = wright_table["TEMPO2"] + barycorr = wright_table["BARYCORR"] # tau Ceti Hipparchos data - tauCet = SkyCoord('01 44 05.1275 -15 56 22.4006', - unit=(u.hour, u.deg), - pm_ra_cosdec=-1721.05*u.mas/u.yr, - pm_dec=854.16*u.mas/u.yr, - distance=Distance(parallax=273.96*u.mas), - radial_velocity=-16.597*u.km/u.s, - obstime=Time(48348.5625, format='mjd')) + tauCet = SkyCoord( + "01 44 05.1275 -15 56 22.4006", + unit=(u.hour, u.deg), + pm_ra_cosdec=-1721.05 * u.mas / u.yr, + pm_dec=854.16 * u.mas / u.yr, + distance=Distance(parallax=273.96 * u.mas), + radial_velocity=-16.597 * u.km / u.s, + obstime=Time(48348.5625, format="mjd"), + ) # CTIO location as used in Wright & Eastmann xyz = u.Quantity([1814985.3, -5213916.8, -3187738.1], u.m) obs = EarthLocation(*xyz) - times = Time(2400000, reduced_jds, format='jd') + times = Time(2400000, reduced_jds, format="jd") tempo2 = tempo2 * speed_of_light barycorr = barycorr * speed_of_light astropy = tauCet.radial_velocity_correction(location=obs, obstime=times) - assert_quantity_allclose(astropy, tempo2, atol=5*u.mm/u.s) - assert_quantity_allclose(astropy, barycorr, atol=5*u.mm/u.s) + assert_quantity_allclose(astropy, tempo2, atol=5 * u.mm / u.s) + assert_quantity_allclose(astropy, barycorr, atol=5 * u.mm / u.s) diff --git a/astropy/coordinates/transformations.py b/astropy/coordinates/transformations.py index c37bf51d7b6..1086a67a43d 100644 --- a/astropy/coordinates/transformations.py +++ b/astropy/coordinates/transformations.py @@ -29,10 +29,17 @@ from astropy import units as u from astropy.utils.exceptions import AstropyWarning -__all__ = ['TransformGraph', 'CoordinateTransform', 'FunctionTransform', - 'BaseAffineTransform', 'AffineTransform', - 'StaticMatrixTransform', 'DynamicMatrixTransform', - 'FunctionTransformWithFiniteDifference', 'CompositeTransform'] +__all__ = [ + "TransformGraph", + "CoordinateTransform", + "FunctionTransform", + "BaseAffineTransform", + "AffineTransform", + "StaticMatrixTransform", + "DynamicMatrixTransform", + "FunctionTransformWithFiniteDifference", + "CompositeTransform", +] def frame_attrs_from_set(frame_set): @@ -84,7 +91,7 @@ def _cached_names(self): if self._cached_names_dct is None: self._cached_names_dct = dct = {} for c in self.frame_set: - nm = getattr(c, 'name', None) + nm = getattr(c, "name", None) if nm is not None: if not isinstance(nm, list): nm = [nm] @@ -166,11 +173,11 @@ def add_transform(self, fromsys, tosys, transform): """ if not inspect.isclass(fromsys): - raise TypeError('fromsys must be a class') + raise TypeError("fromsys must be a class") if not inspect.isclass(tosys): - raise TypeError('tosys must be a class') + raise TypeError("tosys must be a class") if not callable(transform): - raise TypeError('transform must be callable') + raise TypeError("transform must be callable") frame_set = self.frame_set.copy() frame_set.add(fromsys) @@ -192,11 +199,12 @@ def add_transform(self, fromsys, tosys, transform): if attr in tosys.frame_attributes: invalid_frames.update([tosys]) - raise ValueError("Frame(s) {} contain invalid attribute names: {}" - "\nFrame attributes can not conflict with *any* of" - " the frame data component names (see" - " `frame_transform_graph.frame_component_names`)." - .format(list(invalid_frames), invalid_attrs)) + raise ValueError( + f"Frame(s) {list(invalid_frames)} contain invalid attribute names:" + f" {invalid_attrs}\nFrame attributes can not conflict with *any* of" + " the frame data component names (see" + " `frame_transform_graph.frame_component_names`)." + ) self._graph[fromsys][tosys] = transform self.invalidate_cache() @@ -222,9 +230,9 @@ def remove_transform(self, fromsys, tosys, transform): """ if fromsys is None or tosys is None: if not (tosys is None and fromsys is None): - raise ValueError('fromsys and tosys must both be None if either are') + raise ValueError("fromsys and tosys must both be None if either are") if transform is None: - raise ValueError('cannot give all Nones to remove_transform') + raise ValueError("cannot give all Nones to remove_transform") # search for the requested transform by brute force and remove it for a in self._graph: @@ -239,7 +247,7 @@ def remove_transform(self, fromsys, tosys, transform): if fromsys: break else: - raise ValueError(f'Could not find transform {transform} in the graph') + raise ValueError(f"Could not find transform {transform} in the graph") else: if transform is None: @@ -249,8 +257,10 @@ def remove_transform(self, fromsys, tosys, transform): if curr is transform: self._graph[fromsys].pop(tosys) else: - raise ValueError('Current transform from {} to {} is not ' - '{}'.format(fromsys, tosys, transform)) + raise ValueError( + f"Current transform from {fromsys} to {tosys} is not" + f" {transform}" + ) # Remove the subgraph if it is now empty if self._graph[fromsys] == {}: @@ -282,7 +292,7 @@ def find_shortest_path(self, fromsys, tosys): needed. Is ``inf`` if there is no possible path. """ - inf = float('inf') + inf = float("inf") # special-case the 0 or 1-path if tosys is fromsys: @@ -293,7 +303,7 @@ def find_shortest_path(self, fromsys, tosys): # this will also catch the case where tosys is fromsys, but has # a defined transform. t = self._graph[fromsys][tosys] - return [fromsys, tosys], float(t.priority if hasattr(t, 'priority') else 1) + return [fromsys, tosys], float(t.priority if hasattr(t, "priority") else 1) # otherwise, need to construct the path: @@ -328,7 +338,7 @@ def find_shortest_path(self, fromsys, tosys): edgeweights[a] = aew = {} agraph = self._graph[a] for b in agraph: - aew[b] = float(agraph[b].priority if hasattr(agraph[b], 'priority') else 1) + aew[b] = float(getattr(agraph[b], "priority", 1)) # entries in q are [distance, count, nodeobj, pathlist] # count is needed because in py 3.x, tie-breaking fails on the nodes. @@ -364,7 +374,9 @@ def find_shortest_path(self, fromsys, tosys): if q[i][2] == n2: break else: - raise ValueError('n2 not in heap - this should be impossible!') + raise ValueError( + "n2 not in heap - this should be impossible!" + ) newd = d + edgeweights[n][n2] if newd < q[i][0]: @@ -405,9 +417,9 @@ def get_transform(self, fromsys, tosys): """ if not inspect.isclass(fromsys): - raise TypeError('fromsys is not a class') + raise TypeError("fromsys is not a class") if not inspect.isclass(tosys): - raise TypeError('tosys is not a class') + raise TypeError("tosys is not a class") path, distance = self.find_shortest_path(fromsys, tosys) @@ -422,8 +434,9 @@ def get_transform(self, fromsys, tosys): fttuple = (fromsys, tosys) if fttuple not in self._composite_cache: - comptrans = CompositeTransform(transforms, fromsys, tosys, - register_graph=False) + comptrans = CompositeTransform( + transforms, fromsys, tosys, register_graph=False + ) self._composite_cache[fttuple] = comptrans return self._composite_cache[fttuple] @@ -457,8 +470,15 @@ def get_names(self): """ return list(self._cached_names.keys()) - def to_dot_graph(self, priorities=True, addnodes=[], savefn=None, - savelayout='plain', saveformat=None, color_edges=True): + def to_dot_graph( + self, + priorities=True, + addnodes=[], + savefn=None, + savelayout="plain", + saveformat=None, + color_edges=True, + ): """ Converts this transform graph to the graphviz_ DOT format. @@ -508,14 +528,18 @@ def to_dot_graph(self, priorities=True, addnodes=[], savefn=None, if node not in nodes: nodes.append(node) nodenames = [] - invclsaliases = {f: [k for k, v in self._cached_names.items() if v == f] - for f in self.frame_set} + invclsaliases = { + f: [k for k, v in self._cached_names.items() if v == f] + for f in self.frame_set + } for n in nodes: if n in invclsaliases: - aliases = '`\\n`'.join(invclsaliases[n]) - nodenames.append('{0} [shape=oval label="{0}\\n`{1}`"]'.format(n.__name__, aliases)) + aliases = "`\\n`".join(invclsaliases[n]) + nodenames.append( + '{0} [shape=oval label="{0}\\n`{1}`"]'.format(n.__name__, aliases) + ) else: - nodenames.append(n.__name__ + '[ shape=oval ]') + nodenames.append(n.__name__ + "[ shape=oval ]") edgenames = [] # Now the edges @@ -523,48 +547,51 @@ def to_dot_graph(self, priorities=True, addnodes=[], savefn=None, agraph = self._graph[a] for b in agraph: transform = agraph[b] - pri = transform.priority if hasattr(transform, 'priority') else 1 - color = trans_to_color[transform.__class__] if color_edges else 'black' + pri = transform.priority if hasattr(transform, "priority") else 1 + color = trans_to_color[transform.__class__] if color_edges else "black" edgenames.append((a.__name__, b.__name__, pri, color)) # generate simple dot format graph - lines = ['digraph AstropyCoordinateTransformGraph {'] - lines.append('graph [rankdir=LR]') - lines.append('; '.join(nodenames) + ';') + lines = ["digraph AstropyCoordinateTransformGraph {"] + lines.append("graph [rankdir=LR]") + lines.append("; ".join(nodenames) + ";") for enm1, enm2, weights, color in edgenames: - labelstr_fmt = '[ {0} {1} ]' + labelstr_fmt = "[ {0} {1} ]" if priorities: priority_part = f'label = "{weights}"' else: - priority_part = '' + priority_part = "" color_part = f'color = "{color}"' labelstr = labelstr_fmt.format(priority_part, color_part) - lines.append(f'{enm1} -> {enm2}{labelstr};') + lines.append(f"{enm1} -> {enm2}{labelstr};") - lines.append('') - lines.append('overlap=false') - lines.append('}') - dotgraph = '\n'.join(lines) + lines.append("") + lines.append("overlap=false") + lines.append("}") + dotgraph = "\n".join(lines) if savefn is not None: - if savelayout == 'plain': - with open(savefn, 'w') as f: + if savelayout == "plain": + with open(savefn, "w") as f: f.write(dotgraph) else: args = [savelayout] if saveformat is not None: - args.append('-T' + saveformat) - proc = subprocess.Popen(args, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + args.append("-T" + saveformat) + proc = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) stdout, stderr = proc.communicate(dotgraph) if proc.returncode != 0: - raise OSError('problem running graphviz: \n' + stderr) + raise OSError("problem running graphviz: \n" + stderr) - with open(savefn, 'w') as f: + with open(savefn, "w") as f: f.write(stdout) return dotgraph @@ -599,7 +626,7 @@ def to_networkx_graph(self): agraph = self._graph[a] for b in agraph: transform = agraph[b] - pri = transform.priority if hasattr(transform, 'priority') else 1 + pri = transform.priority if hasattr(transform, "priority") else 1 color = trans_to_color[transform.__class__] nxgraph.add_edge(a, b, weight=pri, color=color) @@ -660,13 +687,16 @@ def f1_to_f2(f1_obj): return f2_obj """ + def deco(func): # this doesn't do anything directly with the transform because # ``register_graph=self`` stores it in the transform graph # automatically - transcls(func, fromsys, tosys, priority=priority, - register_graph=self, **kwargs) + transcls( + func, fromsys, tosys, priority=priority, register_graph=self, **kwargs + ) return func + return deco def _add_merged_transform(self, fromsys, tosys, *furthersys, priority=1): @@ -714,16 +744,25 @@ def _add_merged_transform(self, fromsys, tosys, *furthersys, priority=1): frames = [fromsys, tosys, *furthersys] lastsys = frames[-1] full_path = self.get_transform(fromsys, lastsys) - transforms = [self.get_transform(frame_a, frame_b) - for frame_a, frame_b in zip(frames[:-1], frames[1:])] + transforms = [ + self.get_transform(frame_a, frame_b) + for frame_a, frame_b in zip(frames[:-1], frames[1:]) + ] if None in transforms: raise ValueError("This transformation path is not possible") if len(full_path.transforms) == 1: - raise ValueError(f"A direct transform for {fromsys.__name__}->{lastsys.__name__} already exists") - - self.add_transform(fromsys, lastsys, - CompositeTransform(transforms, fromsys, lastsys, - priority=priority)._as_single_transform()) + raise ValueError( + f"A direct transform for {fromsys.__name__}->{lastsys.__name__} already" + " exists" + ) + + self.add_transform( + fromsys, + lastsys, + CompositeTransform( + transforms, fromsys, lastsys, priority=priority + )._as_single_transform(), + ) @contextmanager def impose_finite_difference_dt(self, dt): @@ -741,7 +780,7 @@ def impose_finite_difference_dt(self, dt): If a quantity, this is the size of the differential used to do the finite difference. If a callable, should accept ``(fromcoord, toframe)`` and return the ``dt`` value. """ - key = 'finite_difference_dt' + key = "finite_difference_dt" saved_settings = [] try: @@ -759,6 +798,7 @@ def impose_finite_difference_dt(self, dt): # <-------------------Define the builtin transform classes--------------------> + class CoordinateTransform(metaclass=ABCMeta): """ An object that transforms a coordinate from one system to another. @@ -782,9 +822,9 @@ class CoordinateTransform(metaclass=ABCMeta): def __init__(self, fromsys, tosys, priority=1, register_graph=None): if not inspect.isclass(fromsys): - raise TypeError('fromsys must be a class') + raise TypeError("fromsys must be a class") if not inspect.isclass(tosys): - raise TypeError('tosys must be a class') + raise TypeError("tosys must be a class") self.fromsys = fromsys self.tosys = tosys @@ -795,11 +835,10 @@ def __init__(self, fromsys, tosys, priority=1, register_graph=None): self.register(register_graph) else: if not inspect.isclass(fromsys) or not inspect.isclass(tosys): - raise TypeError('fromsys and tosys must be classes') + raise TypeError("fromsys and tosys must be classes") self.overlapping_frame_attr_names = overlap = [] - if (hasattr(fromsys, 'frame_attributes') and - hasattr(tosys, 'frame_attributes')): + if hasattr(fromsys, "frame_attributes") and hasattr(tosys, "frame_attributes"): # the if statement is there so that non-frame things might be usable # if it makes sense for from_nm in fromsys.frame_attributes: @@ -894,29 +933,37 @@ class FunctionTransform(CoordinateTransform): def __init__(self, func, fromsys, tosys, priority=1, register_graph=None): if not callable(func): - raise TypeError('func must be callable') + raise TypeError("func must be callable") with suppress(TypeError): sig = signature(func) kinds = [x.kind for x in sig.parameters.values()] - if (len(x for x in kinds if x == sig.POSITIONAL_ONLY) != 2 and - sig.VAR_POSITIONAL not in kinds): - raise ValueError('provided function does not accept two arguments') + if ( + len(x for x in kinds if x == sig.POSITIONAL_ONLY) != 2 + and sig.VAR_POSITIONAL not in kinds + ): + raise ValueError("provided function does not accept two arguments") self.func = func - super().__init__(fromsys, tosys, priority=priority, - register_graph=register_graph) + super().__init__( + fromsys, tosys, priority=priority, register_graph=register_graph + ) def __call__(self, fromcoord, toframe): res = self.func(fromcoord, toframe) if not isinstance(res, self.tosys): - raise TypeError(f'the transformation function yielded {res} but ' - f'should have been of type {self.tosys}') + raise TypeError( + f"the transformation function yielded {res} but " + f"should have been of type {self.tosys}" + ) if fromcoord.data.differentials and not res.data.differentials: - warn("Applied a FunctionTransform to a coordinate frame with " - "differentials, but the FunctionTransform does not handle " - "differentials, so they have been dropped.", AstropyWarning) + warn( + "Applied a FunctionTransform to a coordinate frame with " + "differentials, but the FunctionTransform does not handle " + "differentials, so they have been dropped.", + AstropyWarning, + ) return res @@ -961,10 +1008,17 @@ class FunctionTransformWithFiniteDifference(FunctionTransform): """ - def __init__(self, func, fromsys, tosys, priority=1, register_graph=None, - finite_difference_frameattr_name='obstime', - finite_difference_dt=1*u.second, - symmetric_finite_difference=True): + def __init__( + self, + func, + fromsys, + tosys, + priority=1, + register_graph=None, + finite_difference_frameattr_name="obstime", + finite_difference_dt=1 * u.second, + symmetric_finite_difference=True, + ): super().__init__(func, fromsys, tosys, priority, register_graph) self.finite_difference_frameattr_name = finite_difference_frameattr_name self.finite_difference_dt = finite_difference_dt @@ -985,10 +1039,10 @@ def finite_difference_frameattr_name(self, value): self._diff_attr_in_fromsys = diff_attr_in_fromsys self._diff_attr_in_tosys = diff_attr_in_tosys else: - raise ValueError('Frame attribute name {} is not a frame ' - 'attribute of {} or {}'.format(value, - self.fromsys, - self.tosys)) + raise ValueError( + f"Frame attribute name {value} is not a frame attribute of" + f" {self.fromsys} or {self.tosys}" + ) self._finite_difference_frameattr_name = value def __call__(self, fromcoord, toframe): @@ -1002,25 +1056,38 @@ def __call__(self, fromcoord, toframe): dt = self.finite_difference_dt(fromcoord, toframe) else: dt = self.finite_difference_dt - halfdt = dt/2 + halfdt = dt / 2 - from_diffless = fromcoord.realize_frame(fromcoord.data.without_differentials()) + from_diffless = fromcoord.realize_frame( + fromcoord.data.without_differentials() + ) reprwithoutdiff = supcall(from_diffless, toframe) # first we use the existing differential to compute an offset due to # the already-existing velocity, but in the new frame fromcoord_cart = fromcoord.cartesian if self.symmetric_finite_difference: - fwdxyz = (fromcoord_cart.xyz + - fromcoord_cart.differentials['s'].d_xyz*halfdt) - fwd = supcall(fromcoord.realize_frame(CartesianRepresentation(fwdxyz)), toframe) - backxyz = (fromcoord_cart.xyz - - fromcoord_cart.differentials['s'].d_xyz*halfdt) - back = supcall(fromcoord.realize_frame(CartesianRepresentation(backxyz)), toframe) + fwdxyz = ( + fromcoord_cart.xyz + + fromcoord_cart.differentials["s"].d_xyz * halfdt + ) + fwd = supcall( + fromcoord.realize_frame(CartesianRepresentation(fwdxyz)), toframe + ) + backxyz = ( + fromcoord_cart.xyz + - fromcoord_cart.differentials["s"].d_xyz * halfdt + ) + back = supcall( + fromcoord.realize_frame(CartesianRepresentation(backxyz)), toframe + ) else: - fwdxyz = (fromcoord_cart.xyz + - fromcoord_cart.differentials['s'].d_xyz*dt) - fwd = supcall(fromcoord.realize_frame(CartesianRepresentation(fwdxyz)), toframe) + fwdxyz = ( + fromcoord_cart.xyz + fromcoord_cart.differentials["s"].d_xyz * dt + ) + fwd = supcall( + fromcoord.realize_frame(CartesianRepresentation(fwdxyz)), toframe + ) back = reprwithoutdiff diffxyz = (fwd.cartesian - back.cartesian).xyz / dt @@ -1069,7 +1136,9 @@ def __call__(self, fromcoord, toframe): diffxyz += (fwd.cartesian - back.cartesian).xyz / dt newdiff = CartesianDifferential(diffxyz) - reprwithdiff = reprwithoutdiff.data.to_cartesian().with_differentials(newdiff) + reprwithdiff = reprwithoutdiff.data.to_cartesian().with_differentials( + newdiff + ) return reprwithoutdiff.realize_frame(reprwithdiff) else: return supcall(fromcoord, toframe) @@ -1098,58 +1167,73 @@ def _apply_transform(self, fromcoord, matrix, offset): ) data = fromcoord.data - has_velocity = 's' in data.differentials + has_velocity = "s" in data.differentials # Bail out if no transform is actually requested if matrix is None and offset is None: return data # list of unit differentials - _unit_diffs = (SphericalDifferential._unit_differential, - SphericalCosLatDifferential._unit_differential) - unit_vel_diff = (has_velocity and - isinstance(data.differentials['s'], _unit_diffs)) - rad_vel_diff = (has_velocity and - isinstance(data.differentials['s'], RadialDifferential)) + _unit_diffs = ( + SphericalDifferential._unit_differential, + SphericalCosLatDifferential._unit_differential, + ) + unit_vel_diff = has_velocity and isinstance( + data.differentials["s"], _unit_diffs + ) + rad_vel_diff = has_velocity and isinstance( + data.differentials["s"], RadialDifferential + ) # Some initial checking to short-circuit doing any re-representation if # we're going to fail anyways: if isinstance(data, UnitSphericalRepresentation) and offset is not None: - raise TypeError("Position information stored on coordinate frame " - "is insufficient to do a full-space position " - "transformation (representation class: {})" - .format(data.__class__)) - - elif (has_velocity and (unit_vel_diff or rad_vel_diff) and - offset is not None and 's' in offset.differentials): + raise TypeError( + "Position information stored on coordinate frame " + "is insufficient to do a full-space position " + "transformation (representation class: {data.__class__})" + ) + + elif ( + has_velocity + and (unit_vel_diff or rad_vel_diff) + and offset is not None + and "s" in offset.differentials + ): # Coordinate has a velocity, but it is not a full-space velocity # that we need to do a velocity offset - raise TypeError("Velocity information stored on coordinate frame " - "is insufficient to do a full-space velocity " - "transformation (differential class: {})" - .format(data.differentials['s'].__class__)) + raise TypeError( + "Velocity information stored on coordinate frame is insufficient to do" + " a full-space velocity transformation (differential class:" + f" {data.differentials['s'].__class__})" + ) elif len(data.differentials) > 1: # We should never get here because the frame initializer shouldn't # allow more differentials, but this just adds protection for # subclasses that somehow skip the checks - raise ValueError("Representation passed to AffineTransform contains" - " multiple associated differentials. Only a single" - " differential with velocity units is presently" - " supported (differentials: {})." - .format(str(data.differentials))) + raise ValueError( + "Representation passed to AffineTransform contains multiple associated" + " differentials. Only a single differential with velocity units is" + f" presently supported (differentials: {data.differentials})." + ) # If the representation is a UnitSphericalRepresentation, and this is # just a MatrixTransform, we have to try to turn the differential into a # Unit version of the differential (if no radial velocity) or a # sphericaldifferential with zero proper motion (if only a radial # velocity) so that the matrix operation works - if (has_velocity and isinstance(data, UnitSphericalRepresentation) and - not unit_vel_diff and not rad_vel_diff): + if ( + has_velocity + and isinstance(data, UnitSphericalRepresentation) + and not unit_vel_diff + and not rad_vel_diff + ): # retrieve just velocity differential - unit_diff = data.differentials['s'].represent_as( - data.differentials['s']._unit_differential, data) - data = data.with_differentials({'s': unit_diff}) # updates key + unit_diff = data.differentials["s"].represent_as( + data.differentials["s"]._unit_differential, data + ) + data = data.with_differentials({"s": unit_diff}) # updates key # If it's a RadialDifferential, we flat-out ignore the differentials # This is because, by this point (past the validation above), we can @@ -1161,8 +1245,10 @@ def _apply_transform(self, fromcoord, matrix, offset): # Convert the representation and differentials to cartesian without # having them attached to a frame rep = data.to_cartesian() - diffs = {k: diff.represent_as(CartesianDifferential, data) - for k, diff in data.differentials.items()} + diffs = { + k: diff.represent_as(CartesianDifferential, data) + for k, diff in data.differentials.items() + } rep = rep.with_differentials(diffs) # Only do transform if matrix is specified. This is for speed in @@ -1174,20 +1260,19 @@ def _apply_transform(self, fromcoord, matrix, offset): # TODO: if we decide to allow arithmetic between representations that # contain differentials, this can be tidied up if offset is not None: - newrep = (rep.without_differentials() + - offset.without_differentials()) + newrep = rep.without_differentials() + offset.without_differentials() else: newrep = rep.without_differentials() # We need a velocity (time derivative) and, for now, are strict: the # representation can only contain a velocity differential and no others. if has_velocity and not rad_vel_diff: - veldiff = rep.differentials['s'] # already in Cartesian form + veldiff = rep.differentials["s"] # already in Cartesian form - if offset is not None and 's' in offset.differentials: - veldiff = veldiff + offset.differentials['s'] + if offset is not None and "s" in offset.differentials: + veldiff = veldiff + offset.differentials["s"] - newrep = newrep.with_differentials({'s': veldiff}) + newrep = newrep.with_differentials({"s": veldiff}) if isinstance(fromcoord.data, UnitSphericalRepresentation): # Special-case this because otherwise the return object will think @@ -1198,19 +1283,23 @@ def _apply_transform(self, fromcoord, matrix, offset): # We have to first represent as the Unit types we converted to, # then put the d_distance information back in to the # differentials and re-represent as their original forms - newdiff = newrep.differentials['s'] - _unit_cls = fromcoord.data.differentials['s']._unit_differential + newdiff = newrep.differentials["s"] + _unit_cls = fromcoord.data.differentials["s"]._unit_differential newdiff = newdiff.represent_as(_unit_cls, newrep) kwargs = {comp: getattr(newdiff, comp) for comp in newdiff.components} - kwargs['d_distance'] = fromcoord.data.differentials['s'].d_distance - diffs = {'s': fromcoord.data.differentials['s'].__class__( - copy=False, **kwargs)} + kwargs["d_distance"] = fromcoord.data.differentials["s"].d_distance + diffs = { + "s": fromcoord.data.differentials["s"].__class__( + copy=False, **kwargs + ) + } elif has_velocity and unit_vel_diff: - newdiff = newrep.differentials['s'].represent_as( - fromcoord.data.differentials['s'].__class__, newrep) - diffs = {'s': newdiff} + newdiff = newrep.differentials["s"].represent_as( + fromcoord.data.differentials["s"].__class__, newrep + ) + diffs = {"s": newdiff} else: diffs = newrep.differentials @@ -1228,9 +1317,10 @@ def _apply_transform(self, fromcoord, matrix, offset): # unit differential so that the units work out (the distance length # unit shouldn't appear in the resulting proper motions) - diff_cls = fromcoord.data.differentials['s'].__class__ - newrep = newrep.represent_as(fromcoord.data.__class__, - diff_cls._dimensional_differential) + diff_cls = fromcoord.data.differentials["s"].__class__ + newrep = newrep.represent_as( + fromcoord.data.__class__, diff_cls._dimensional_differential + ) newrep = newrep.represent_as(fromcoord.data.__class__, diff_cls) # We pulled the radial differential off of the representation @@ -1239,8 +1329,7 @@ def _apply_transform(self, fromcoord, matrix, offset): # having a RadialDifferential if has_velocity and rad_vel_diff: newrep = newrep.represent_as(fromcoord.data.__class__) - newrep = newrep.with_differentials( - {'s': fromcoord.data.differentials['s']}) + newrep = newrep.with_differentials({"s": fromcoord.data.differentials["s"]}) return newrep @@ -1289,15 +1378,14 @@ class AffineTransform(BaseAffineTransform): """ - def __init__(self, transform_func, fromsys, tosys, priority=1, - register_graph=None): - + def __init__(self, transform_func, fromsys, tosys, priority=1, register_graph=None): if not callable(transform_func): - raise TypeError('transform_func is not callable') + raise TypeError("transform_func is not callable") self.transform_func = transform_func - super().__init__(fromsys, tosys, priority=priority, - register_graph=register_graph) + super().__init__( + fromsys, tosys, priority=priority, register_graph=register_graph + ) def _affine_params(self, fromcoord, toframe): return self.transform_func(fromcoord, toframe) @@ -1342,10 +1430,11 @@ def __init__(self, matrix, fromsys, tosys, priority=1, register_graph=None): self.matrix = np.array(matrix) if self.matrix.shape != (3, 3): - raise ValueError('Provided matrix is not 3 x 3') + raise ValueError("Provided matrix is not 3 x 3") - super().__init__(fromsys, tosys, priority=priority, - register_graph=register_graph) + super().__init__( + fromsys, tosys, priority=priority, register_graph=register_graph + ) def _affine_params(self, fromcoord, toframe): return self.matrix, None @@ -1383,14 +1472,14 @@ class DynamicMatrixTransform(BaseAffineTransform): """ - def __init__(self, matrix_func, fromsys, tosys, priority=1, - register_graph=None): + def __init__(self, matrix_func, fromsys, tosys, priority=1, register_graph=None): if not callable(matrix_func): - raise TypeError('matrix_func is not callable') + raise TypeError("matrix_func is not callable") self.matrix_func = matrix_func - super().__init__(fromsys, tosys, priority=priority, - register_graph=register_graph) + super().__init__( + fromsys, tosys, priority=priority, register_graph=register_graph + ) def _affine_params(self, fromcoord, toframe): return self.matrix_func(fromcoord, toframe), None @@ -1426,10 +1515,18 @@ class CompositeTransform(CoordinateTransform): """ - def __init__(self, transforms, fromsys, tosys, priority=1, - register_graph=None, collapse_static_mats=True): - super().__init__(fromsys, tosys, priority=priority, - register_graph=register_graph) + def __init__( + self, + transforms, + fromsys, + tosys, + priority=1, + register_graph=None, + collapse_static_mats=True, + ): + super().__init__( + fromsys, tosys, priority=priority, register_graph=register_graph + ) if collapse_static_mats: transforms = self._combine_statics(transforms) @@ -1445,8 +1542,9 @@ def _combine_statics(self, transforms): for currtrans in transforms: lasttrans = newtrans[-1] if len(newtrans) > 0 else None - if (isinstance(lasttrans, StaticMatrixTransform) and - isinstance(currtrans, StaticMatrixTransform)): + if isinstance(lasttrans, StaticMatrixTransform) and isinstance( + currtrans, StaticMatrixTransform + ): newtrans[-1] = StaticMatrixTransform( currtrans.matrix @ lasttrans.matrix, lasttrans.fromsys, @@ -1494,13 +1592,17 @@ def _as_single_transform(self): `~astropy.coordinates.transformations.FunctionTransformWithFiniteDifference`. """ # Create a list of the transforms including flattening any constituent CompositeTransform - transforms = [t if not isinstance(t, CompositeTransform) else t._as_single_transform() - for t in self.transforms] + transforms = [ + t if not isinstance(t, CompositeTransform) else t._as_single_transform() + for t in self.transforms + ] if all([isinstance(t, BaseAffineTransform) for t in transforms]): # Check if there may be an origin shift - fixed_origin = all([isinstance(t, (StaticMatrixTransform, DynamicMatrixTransform)) - for t in transforms]) + fixed_origin = all( + isinstance(t, (StaticMatrixTransform, DynamicMatrixTransform)) + for t in transforms + ) # Dynamically define the transformation function def single_transform(from_coo, to_frame): @@ -1510,10 +1612,15 @@ def single_transform(from_coo, to_frame): # Create a merged attribute dictionary for any intermediate frames # For any attributes shared by the "from"/"to" frames, the "to" frame takes # precedence because this is the same choice implemented in __call__() - merged_attr = {name: getattr(from_coo, name) - for name in from_coo.frame_attributes} - merged_attr.update({name: getattr(to_frame, name) - for name in to_frame.frame_attributes}) + merged_attr = { + name: getattr(from_coo, name) for name in from_coo.frame_attributes + } + merged_attr.update( + { + name: getattr(to_frame, name) + for name in to_frame.frame_attributes + } + ) affine_params = (None, None) # Step through each transform step (frame A -> frame B) @@ -1521,26 +1628,37 @@ def single_transform(from_coo, to_frame): # Extract the relevant attributes for frame A if i == 0: # If frame A is actually the initial frame, preserve its attributes - a_attr = {name: getattr(from_coo, name) - for name in from_coo.frame_attributes} + a_attr = { + name: getattr(from_coo, name) + for name in from_coo.frame_attributes + } else: - a_attr = {k: v for k, v in merged_attr.items() - if k in t.fromsys.frame_attributes} + a_attr = { + k: v + for k, v in merged_attr.items() + if k in t.fromsys.frame_attributes + } # Extract the relevant attributes for frame B - b_attr = {k: v for k, v in merged_attr.items() - if k in t.tosys.frame_attributes} + b_attr = { + k: v + for k, v in merged_attr.items() + if k in t.tosys.frame_attributes + } # Obtain the affine parameters for the transform # Note that we insert some dummy data into frame A because the transformation # machinery requires there to be data present. Removing that limitation # is a possible TODO, but some care would need to be taken because some affine # transforms have branching code depending on the presence of differentials. - next_affine_params = t._affine_params(t.fromsys(from_coo.data, **a_attr), - t.tosys(**b_attr)) + next_affine_params = t._affine_params( + t.fromsys(from_coo.data, **a_attr), t.tosys(**b_attr) + ) # Combine the affine parameters with the running set - affine_params = _combine_affine_params(affine_params, next_affine_params) + affine_params = _combine_affine_params( + affine_params, next_affine_params + ) # If there is no origin shift, return only the matrix return affine_params[0] if fixed_origin else affine_params @@ -1556,7 +1674,9 @@ def single_transform(from_coo, to_frame): transform_type = FunctionTransformWithFiniteDifference - return transform_type(single_transform, self.fromsys, self.tosys, priority=self.priority) + return transform_type( + single_transform, self.fromsys, self.tosys, priority=self.priority + ) def _combine_affine_params(params, next_params): @@ -1583,12 +1703,12 @@ def _combine_affine_params(params, next_params): # Calculate the new displacement vector if next_vec is not None: - if 's' in vec.differentials and 's' in next_vec.differentials: + if "s" in vec.differentials and "s" in next_vec.differentials: # Adding vectors with velocities takes more steps # TODO: Add support in representation.py - new_vec_velocity = vec.differentials['s'] + next_vec.differentials['s'] + new_vec_velocity = vec.differentials["s"] + next_vec.differentials["s"] new_vec = vec.without_differentials() + next_vec.without_differentials() - new_vec = new_vec.with_differentials({'s': new_vec_velocity}) + new_vec = new_vec.with_differentials({"s": new_vec_velocity}) else: new_vec = vec + next_vec else: @@ -1601,8 +1721,8 @@ def _combine_affine_params(params, next_params): # map class names to colorblind-safe colors trans_to_color = {} -trans_to_color[AffineTransform] = '#555555' # gray -trans_to_color[FunctionTransform] = '#783001' # dark red-ish/brown -trans_to_color[FunctionTransformWithFiniteDifference] = '#d95f02' # red-ish -trans_to_color[StaticMatrixTransform] = '#7570b3' # blue-ish -trans_to_color[DynamicMatrixTransform] = '#1b9e77' # green-ish +trans_to_color[AffineTransform] = "#555555" # gray +trans_to_color[FunctionTransform] = "#783001" # dark red-ish/brown +trans_to_color[FunctionTransformWithFiniteDifference] = "#d95f02" # red-ish +trans_to_color[StaticMatrixTransform] = "#7570b3" # blue-ish +trans_to_color[DynamicMatrixTransform] = "#1b9e77" # green-ish diff --git a/pyproject.toml b/pyproject.toml index b1348744268..b73ff57759e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,6 @@ force-exclude = ''' | astropy/_erfa | astropy/constants | astropy/convolution - | astropy/coordinates | astropy/extern | astropy/io/fits | astropy/modeling @@ -71,6 +70,7 @@ force-exclude = ''' | astropy/version.py | conftest.py | setup.py + | astropy/coordinates/.*tab.py ) '''