# -*- coding: utf-8 -*- # PyMeeus: Python module implementing astronomical algorithms. # Copyright (C) 2018 Dagoberto Salazar # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from math import ( sqrt, sin, cos, tan, atan, atan2, asin, acos, radians, pi, copysign, degrees ) from pymeeus.base import TOL, iint from pymeeus.Angle import Angle from pymeeus.Epoch import Epoch, JDE2000 from pymeeus.Interpolation import Interpolation """ .. module:: Coordinates :synopsis: Module including different functions to handle coordinates :license: GNU Lesser General Public License v3 (LGPLv3) .. moduleauthor:: Dagoberto Salazar """ NUTATION_ARG_TABLE = [ [0, 0, 0, 0, 1], [-2, 0, 0, 2, 2], [0, 0, 0, 2, 2], [0, 0, 0, 0, 2], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [-2, 1, 0, 2, 2], [0, 0, 0, 2, 1], [0, 0, 1, 2, 2], [-2, -1, 0, 2, 2], [-2, 0, 1, 0, 0], [-2, 0, 0, 2, 1], [0, 0, -1, 2, 2], [2, 0, 0, 0, 0], [0, 0, 1, 0, 1], [2, 0, -1, 2, 2], [0, 0, -1, 0, 1], [0, 0, 1, 2, 1], [-2, 0, 2, 0, 0], [0, 0, -2, 2, 1], [2, 0, 0, 2, 2], [0, 0, 2, 2, 2], [0, 0, 2, 0, 0], [-2, 0, 1, 2, 2], [0, 0, 0, 2, 0], [-2, 0, 0, 2, 0], [0, 0, -1, 2, 1], [0, 2, 0, 0, 0], [2, 0, -1, 0, 1], [-2, 2, 0, 2, 2], [0, 1, 0, 0, 1], [-2, 0, 1, 0, 1], [0, -1, 0, 0, 1], [0, 0, 2, -2, 0], [2, 0, -1, 2, 1], [2, 0, 1, 2, 2], [0, 1, 0, 2, 2], [-2, 1, 1, 0, 0], [0, -1, 0, 2, 2], [2, 0, 0, 2, 1], [2, 0, 1, 0, 0], [-2, 0, 2, 2, 2], [-2, 0, 1, 2, 1], [2, 0, -2, 0, 1], [2, 0, 0, 0, 1], [0, -1, 1, 0, 0], [-2, -1, 0, 2, 1], [-2, 0, 0, 0, 1], [0, 0, 2, 2, 1], [-2, 0, 2, 0, 1], [-2, 1, 0, 2, 1], [0, 0, 1, -2, 0], [-1, 0, 1, 0, 0], [-2, 1, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 1, 2, 0], [0, 0, -2, 2, 2], [-1, -1, 1, 0, 0], [0, 1, 1, 0, 0], [0, -1, 1, 2, 2], [2, -1, -1, 2, 2], [0, 0, 3, 2, 2], [2, -1, 0, 2, 2], ] """This table contains the periodic terms for the argument of the nutation. In Meeus' book this is Table 22.A and can be found in pages 145-146.""" NUTATION_SINE_COEF_TABLE = [ [-171996.0, -174.2], [-13187.0, -1.6], [-2274.0, -0.2], [2062.0, 0.2], [1426.0, -3.4], [712.0, 0.1], [-517.0, 1.2], [-386.0, -0.4], [-301.0, 0.0], [217.0, -0.5], [-158.0, 0.0], [129.0, 0.1], [123.0, 0.0], [63.0, 0.0], [63.0, 0.1], [-59.0, 0.0], [-58.0, -0.1], [-51.0, 0.0], [48.0, 0.0], [46.0, 0.0], [-38.0, 0.0], [-31.0, 0.0], [29.0, 0.0], [29.0, 0.0], [26.0, 0.0], [-22.0, 0.0], [21.0, 0.0], [17.0, -0.1], [16.0, 0.0], [-16.0, 0.1], [-15.0, 0.0], [-13.0, 0.0], [-12.0, 0.0], [11.0, 0.0], [-10.0, 0.0], [-8.0, 0.0], [7.0, 0.0], [-7.0, 0.0], [-7.0, 0.0], [-7.0, 0.0], [6.0, 0.0], [6.0, 0.0], [6.0, 0.0], [-6.0, 0.0], [-6.0, 0.0], [5.0, 0.0], [-5.0, 0.0], [-5.0, 0.0], [-5.0, 0.0], [4.0, 0.0], [4.0, 0.0], [4.0, 0.0], [-4.0, 0.0], [-4.0, 0.0], [-4.0, 0.0], [3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], ] """This table contains the periodic terms for the coefficients of the sine of the argument of the nutation, and they are used to compute Delta psi. Units are in 0.0001''. In Meeus' book this is Table 22.A and can be found in pages 145-146.""" NUTATION_COSINE_COEF_TABLE = [ [92025.0, 8.9], [5736.0, -3.1], [977.0, -0.5], [-895.0, 0.5], [54.0, -0.1], [-7.0, 0.0], [224.0, -0.6], [200.0, 0.0], [129.0, -0.1], [-95.0, 0.3], [0.0, 0.0], [-70.0, 0.0], [-53.0, 0.0], [0.0, 0.0], [-33.0, 0.0], [26.0, 0.0], [32.0, 0.0], [27.0, 0.0], [0.0, 0.0], [-24.0, 0.0], [16.0, 0.0], [13.0, 0.0], [0.0, 0.0], [-12.0, 0.0], [0.0, 0.0], [0.0, 0.0], [-10.0, 0.0], [0.0, 0.0], [-8.0, 0.0], [7.0, 0.0], [9.0, 0.0], [7.0, 0.0], [6.0, 0.0], [0.0, 0.0], [5.0, 0.0], [3.0, 0.0], [-3.0, 0.0], [0.0, 0.0], [3.0, 0.0], [3.0, 0.0], [0.0, 0.0], [-3.0, 0.0], [-3.0, 0.0], [3.0, 0.0], [3.0, 0.0], [0.0, 0.0], [3.0, 0.0], [3.0, 0.0], [3.0, 0.0], ] """This table contains the periodic terms for the coefficients of the cosine of the argument of the nutation, and they are used to compute Delta epsilon. Units are in 0.0001''. In Meeus' book this is Table 22.A and can be found in pages 145-146.""" def mean_obliquity(*args, **kwargs): """This function computes the mean obliquity (epsilon0) at the provided date. This function internally uses an :class:`Epoch` object, and the **utc** argument then controls the way the UTC->TT conversion is handled for that object. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. :param args: Either :class:`Epoch`, date, datetime or year, month, day values, by themselves or inside a tuple or list :type args: int, float, :py:class:`Epoch`, datetime, date, tuple, list :param utc: Whether the provided epoch is a civil time (UTC) or TT :type utc: bool :param leap_seconds: This is the value to be used in the UTC->TAI conversion, instead of taking it from internal leap seconds table. :type leap_seconds: int, float :returns: The mean obliquity of the ecliptic, as an :class:`Angle` :rtype: :class:`Angle` :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> e0 = mean_obliquity(1987, 4, 10) >>> a = e0.dms_tuple() >>> a[0] 23 >>> a[1] 26 >>> round(a[2], 3) 27.407 """ # Get the Epoch object corresponding to input parameters t = Epoch.check_input_date(*args, **kwargs) # Let's redefine u in units of 100 Julian centuries from Epoch J2000.0 u = (t.jde() - 2451545.0) / 3652500.0 epsilon0 = Angle(23, 26, 21.448) delta = u * ( -4680.93 + u * ( -1.55 + u * ( 1999.25 + u * ( -51.38 + u * ( -249.67 + u * (-39.05 + u * (7.12 + u * (27.87 + u * (5.79 + u * 2.45)))) ) ) ) ) ) delta = Angle(0, 0, delta) epsilon0 += delta return epsilon0 def true_obliquity(*args, **kwargs): """This function computes the true obliquity (epsilon) at the provided date. The true obliquity is the mean obliquity (epsilon0) plus the correction provided by the nutation in obliquity (Delta epsilon). This function internally uses an :class:`Epoch` object, and the **utc** argument then controls the way the UTC->TT conversion is handled for that object. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. :param args: Either :class:`Epoch`, date, datetime or year, month, day values, by themselves or inside a tuple or list :type args: int, float, :py:class:`Epoch`, datetime, date, tuple, list :param utc: Whether the provided epoch is a civil time (UTC) or TT :type utc: bool :param leap_seconds: This is the value to be used in the UTC->TAI conversion, instead of taking it from internal leap seconds table. :type leap_seconds: int, float :returns: The true obliquity of the ecliptic, as an Angle :rtype: :class:`Angle` :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> epsilon = true_obliquity(1987, 4, 10) >>> a = epsilon.dms_tuple() >>> a[0] 23 >>> a[1] 26 >>> round(a[2], 3) 36.849 """ epsilon0 = mean_obliquity(*args, **kwargs) delta_epsilon = nutation_obliquity(*args, **kwargs) return epsilon0 + delta_epsilon def nutation_longitude(*args, **kwargs): """This function computes the nutation in longitude (Delta psi) at the provided date. This function internally uses an :class:`Epoch` object, and the **utc** argument then controls the way the UTC->TT conversion is handled for that object. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. :param args: Either :class:`Epoch`, date, datetime or year, month, day values, by themselves or inside a tuple or list :type args: int, float, :py:class:`Epoch`, datetime, date, tuple, list :param utc: Whether the provided epoch is a civil time (UTC) or TT :type utc: bool :param leap_seconds: This is the value to be used in the UTC->TAI conversion, instead of taking it from internal leap seconds table. :type leap_seconds: int, float :returns: The nutation in longitude (Delta psi), as an Angle :rtype: :class:`Angle` :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> dpsi = nutation_longitude(1987, 4, 10) >>> a = dpsi.dms_tuple() >>> a[0] 0 >>> a[1] 0 >>> round(a[2], 3) 3.788 >>> a[3] -1.0 """ # Get the Epoch object corresponding to input parameters t = Epoch.check_input_date(*args, **kwargs) # Let's redefine t in units of Julian centuries from Epoch J2000.0 t = (t.jde() - 2451545.0) / 36525.0 # Let's compute the mean elongation of the Moon from the Sun d = 297.85036 + t * (445267.111480 + t * (-0.0019142 + t / 189474.0)) d = Angle(d) # Convert into an Angle: It is easier to handle # Compute the mean anomaly of the Sun (from Earth) m = 357.52772 + t * (35999.050340 + t * (-0.0001603 - t / 300000.0)) m = Angle(m) # Compute the mean anomaly of the Moon mprime = 134.96298 + t * (477198.867398 + t * (0.0086972 + t / 56250.0)) mprime = Angle(mprime) # Now, let's compute the Moon's argument of latitude f = 93.27191 + t * (483202.017538 + t * (-0.0036825 + t / 327270.0)) f = Angle(f) # And finally, the longitude of the ascending node of the Moon's mean # orbit on the ecliptic, measured from the mean equinox of date omega = 125.04452 + t * (-1934.136261 + t * (0.0020708 + t / 450000.0)) omega = Angle(omega) # Let's store this results in a list, in preparation for using tables arguments = [d, m, mprime, f, omega] # Now is time of using the nutation tables deltapsi = 0.0 for i, value in enumerate(NUTATION_SINE_COEF_TABLE): argument = Angle() coeff = 0.0 for j in range(5): if NUTATION_ARG_TABLE[i][j]: # Avoid multiplications by zero argument += NUTATION_ARG_TABLE[i][j] * arguments[j] coeff = value[0] if value[1]: coeff += value[1] * t deltapsi += (coeff * sin(argument.rad())) / 10000.0 return Angle(0, 0, deltapsi) def nutation_obliquity(*args, **kwargs): """This function computes the nutation in obliquity (Delta epsilon) at the provided date. This function internally uses an :class:`Epoch` object, and the **utc** argument then controls the way the UTC->TT conversion is handled for that object. If **leap_seconds** argument is set to a value different than zero, then that value will be used for the UTC->TAI conversion, and the internal leap seconds table will be bypassed. :param args: Either :class:`Epoch`, date, datetime or year, month, day values, by themselves or inside a tuple or list :type args: int, float, :py:class:`Epoch`, datetime, date, tuple, list :param utc: Whether the provided epoch is a civil time (UTC) or TT :type utc: bool :param leap_seconds: This is the value to be used in the UTC->TAI conversion, instead of taking it from internal leap seconds table. :type leap_seconds: int, float :returns: The nutation in obliquity (Delta epsilon), as an :class:`Angle` :rtype: :class:`Angle` :raises: ValueError if input values are in the wrong range. :raises: TypeError if input values are of wrong type. >>> depsilon = nutation_obliquity(1987, 4, 10) >>> a = depsilon.dms_tuple() >>> a[0] 0 >>> a[1] 0 >>> round(a[2], 3) 9.443 >>> a[3] 1.0 """ # Get the Epoch object corresponding to input parameters t = Epoch.check_input_date(*args, **kwargs) # Let's redefine t in units of Julian centuries from Epoch J2000.0 t = (t.jde() - 2451545.0) / 36525.0 # Let's compute the mean elongation of the Moon from the Sun d = 297.85036 + t * (445267.111480 + t * (-0.0019142 + t / 189474.0)) d = Angle(d) # Convert into an Angle: It is easier to handle # Compute the mean anomaly of the Sun (from Earth) m = 357.52772 + t * (35999.050340 + t * (-0.0001603 - t / 300000.0)) m = Angle(m) # Compute the mean anomaly of the Moon mprime = 134.96298 + t * (477198.867398 + t * (0.0086972 + t / 56250.0)) mprime = Angle(mprime) # Now, let's compute the Moon's argument of latitude f = 93.27191 + t * (483202.017538 + t * (-0.0036825 + t / 327270.0)) f = Angle(f) # And finally, the longitude of the ascending node of the Moon's mean # orbit on the ecliptic, measured from the mean equinox of date omega = 125.04452 + t * (-1934.136261 + t * (0.0020708 + t / 450000.0)) omega = Angle(omega) # Let's store this results in a list, in preparation for using tables arguments = [d, m, mprime, f, omega] # Now is time of using the nutation tables deltaepsilon = 0.0 for i, value in enumerate(NUTATION_COSINE_COEF_TABLE): argument = Angle() coeff = 0.0 for j in range(5): if NUTATION_ARG_TABLE[i][j]: # Avoid multiplications by zero argument += NUTATION_ARG_TABLE[i][j] * arguments[j] coeff = value[0] if value[1]: coeff += value[1] * t deltaepsilon += (coeff * cos(argument.rad())) / 10000.0 return Angle(0, 0, deltaepsilon) def precession_equatorial( start_epoch, final_epoch, start_ra, start_dec, p_motion_ra=0.0, p_motion_dec=0.0 ): """This function converts the equatorial coordinates (right ascension and declination) given for an epoch and a equinox, to the corresponding values for another epoch and equinox. Only the **mean** positions, i.e. the effects of precession and proper motion, are considered here. :param start_epoch: Initial epoch when initial coordinates are given :type start_epoch: :py:class:`Epoch` :param final_epoch: Final epoch for when coordinates are going to be computed :type final_epoch: :py:class:`Epoch` :param start_ra: Initial right ascension :type start_ra: :py:class:`Angle` :param start_dec: Initial declination :type start_dec: :py:class:`Angle` :param p_motion_ra: Proper motion in right ascension, in degrees per year. Zero by default. :type p_motion_ra: :py:class:`Angle` :param p_motion_dec: Proper motion in declination, in degrees per year. Zero by default. :type p_motion_dec: :py:class:`Angle` :returns: Equatorial coordinates (right ascension, declination, in that order) corresponding to the final epoch, given as two objects :class:`Angle` inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> start_epoch = JDE2000 >>> final_epoch = Epoch(2028, 11, 13.19) >>> alpha0 = Angle(2, 44, 11.986, ra=True) >>> delta0 = Angle(49, 13, 42.48) >>> pm_ra = Angle(0, 0, 0.03425, ra=True) >>> pm_dec = Angle(0, 0, -0.0895) >>> alpha, delta = precession_equatorial(start_epoch, final_epoch, alpha0, ... delta0, pm_ra, pm_dec) >>> print(alpha.ra_str(False, 3)) 2:46:11.331 >>> print(delta.dms_str(False, 2)) 49:20:54.54 """ # First check that input values are of correct types if not ( isinstance(start_epoch, Epoch) and isinstance(final_epoch, Epoch) and isinstance(start_ra, Angle) and isinstance(start_dec, Angle) ): raise TypeError("Invalid input types") if isinstance(p_motion_ra, (int, float)): p_motion_ra = Angle(p_motion_ra) if isinstance(p_motion_dec, (int, float)): p_motion_dec = Angle(p_motion_dec) if not (isinstance(p_motion_ra, Angle) and isinstance(p_motion_dec, Angle)): raise TypeError("Invalid input types") tt = (start_epoch - JDE2000) / 36525.0 t = (final_epoch - start_epoch) / 36525.0 # Correct starting coordinates by proper motion start_ra += p_motion_ra * t * 100.0 start_dec += p_motion_dec * t * 100.0 # Compute the conversion parameters zeta = t * ( (2306.2181 + tt * (1.39656 - 0.000139 * tt)) + t * ((0.30188 - 0.000344 * tt) + 0.017998 * t) ) z = t * ( (2306.2181 + tt * (1.39656 - 0.000139 * tt)) + t * ((1.09468 + 0.000066 * tt) + 0.018203 * t) ) theta = t * ( 2004.3109 + tt * (-0.85330 - 0.000217 * tt) + t * (-(0.42665 + 0.000217 * tt) - 0.041833 * t) ) # Redefine the former values as Angles zeta = Angle(0, 0, zeta) z = Angle(0, 0, z) theta = Angle(0, 0, theta) a = cos(start_dec.rad()) * sin(start_ra.rad() + zeta.rad()) b = cos(theta.rad()) * cos(start_dec.rad()) * cos( start_ra.rad() + zeta.rad() ) - sin(theta.rad()) * sin(start_dec.rad()) c = sin(theta.rad()) * cos(start_dec.rad()) * cos( start_ra.rad() + zeta.rad() ) + cos(theta.rad()) * sin(start_dec.rad()) final_ra = atan2(a, b) + z.rad() if start_dec > 85.0: # Coordinates are close to the pole final_dec = sqrt(a * a + b * b) else: final_dec = asin(c) # Convert results to Angles. Please note results are in radians final_ra = Angle(final_ra, radians=True) final_dec = Angle(final_dec, radians=True) return (final_ra, final_dec) def precession_ecliptical( start_epoch, final_epoch, start_lon, start_lat, p_motion_lon=0.0, p_motion_lat=0.0 ): """This function converts the ecliptical coordinates (longitude and latitude) given for an epoch and a equinox, to the corresponding values for another epoch and equinox. Only the **mean** positions, i.e. the effects of precession and proper motion, are considered here. :param start_epoch: Initial epoch when initial coordinates are given :type start_epoch: :py:class:`Epoch` :param final_epoch: Final epoch for when coordinates are going to be computed :type final_epoch: :py:class:`Epoch` :param start_lon: Initial longitude :type start_lon: :py:class:`Angle` :param start_lat: Initial latitude :type start_lat: :py:class:`Angle` :param p_motion_lon: Proper motion in longitude, in degrees per year. Zero by default. :type p_motion_lon: :py:class:`Angle` :param p_motion_lat: Proper motion in latitude, in degrees per year. Zero by default. :type p_motion_lat: :py:class:`Angle` :returns: Ecliptical coordinates (longitude, latitude, in that order) corresponding to the final epoch, given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> start_epoch = JDE2000 >>> final_epoch = Epoch(-214, 6, 30.0) >>> lon0 = Angle(149.48194) >>> lat0 = Angle(1.76549) >>> lon, lat = precession_ecliptical(start_epoch, final_epoch, lon0, lat0) >>> print(round(lon(), 3)) 118.704 >>> print(round(lat(), 3)) 1.615 """ # First check that input values are of correct types if not ( isinstance(start_epoch, Epoch) and isinstance(final_epoch, Epoch) and isinstance(start_lon, Angle) and isinstance(start_lat, Angle) ): raise TypeError("Invalid input types") if isinstance(p_motion_lon, (int, float)): p_motion_lon = Angle(p_motion_lon) if isinstance(p_motion_lat, (int, float)): p_motion_lat = Angle(p_motion_lat) if not (isinstance(p_motion_lon, Angle) and isinstance(p_motion_lat, Angle)): raise TypeError("Invalid input types") tt = (start_epoch - JDE2000) / 36525.0 t = (final_epoch - start_epoch) / 36525.0 # Correct starting coordinates by proper motion start_lon += p_motion_lon * t * 100.0 start_lat += p_motion_lat * t * 100.0 # Compute the conversion parameters eta = t * ( (47.0029 + tt * (-0.06603 + 0.000598 * tt)) + t * ((-0.03302 + 0.000598 * tt) + 0.00006 * t) ) pie = tt * (3289.4789 + 0.60622 * tt) + t * ( -(869.8089 + 0.50491 * tt) + 0.03536 * t ) p = t * ( 5029.0966 + tt * (2.22226 - 0.000042 * tt) + t * (1.11113 - 0.000042 * tt - 0.000006 * t) ) eta = Angle(0, 0, eta) pie = Angle(0, 0, pie) p = Angle(0, 0, p) # But beware!: There is still a missing constant for pie. We didn't add # it before because of the mismatch between degrees and seconds pie += 174.876384 a = (cos(eta.rad()) * cos(start_lat.rad()) * sin(pie.rad() - start_lon.rad()) - sin(eta.rad()) * sin(start_lat.rad())) b = cos(start_lat.rad()) * cos(pie.rad() - start_lon.rad()) c = cos(eta.rad()) * sin(start_lat.rad()) + sin(eta.rad()) * cos( start_lat.rad() ) * sin(pie.rad() - start_lon.rad()) final_lon = p.rad() + pie.rad() - atan2(a, b) final_lat = asin(c) # Convert results to Angles. Please note results are in radians final_lon = Angle(final_lon, radians=True) final_lat = Angle(final_lat, radians=True) return (final_lon, final_lat) def p_motion_equa2eclip(p_motion_ra, p_motion_dec, ra, dec, lat, epsilon): """It is usual that proper motions are given in equatorial coordinates, not in ecliptical ones. Therefore, this function converts the provided proper motions in equatorial coordinates to the corresponding ones in ecliptical coordinates. :param p_motion_ra: Proper motion in right ascension, in degrees per year, as an :class:`Angle` object :type p_motion_ra: :py:class:`Angle` :param p_motion_dec: Proper motion in declination, in degrees per year, as an :class:`Angle` object :type p_motion_dec: :py:class:`Angle` :param ra: Right ascension of the astronomical object, as degrees in an :class:`Angle` object :type ra: :py:class:`Angle` :param dec: Declination of the astronomical object, as degrees in an :class:`Angle` object :type dec: :py:class:`Angle` :param lat: Ecliptical latitude of the astronomical object, as degrees in an :class:`Angle` object :type lat: :py:class:`Angle` :param epsilon: Obliquity of the ecliptic :type epsilon: :py:class:`Angle` :returns: Proper motions in ecliptical longitude and latitude (in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not ( isinstance(p_motion_ra, Angle) and isinstance(p_motion_dec, Angle) and isinstance(ra, Angle) and isinstance(dec, Angle) and isinstance(lat, Angle) and isinstance(epsilon, Angle) ): raise TypeError("Invalid input types") pm_ra = p_motion_ra.rad() pm_dec = p_motion_dec.rad() s_eps = sin(epsilon.rad()) c_eps = cos(epsilon.rad()) s_ra = sin(ra.rad()) c_ra = cos(ra.rad()) s_dec = sin(dec.rad()) c_dec = cos(dec.rad()) c_lat = cos(lat.rad()) se_ca = s_eps * c_ra se_sd_sa = s_eps * s_dec * s_ra pa_cd = pm_ra * c_dec ce_cd = c_eps * c_dec cl2 = c_lat * c_lat p_motion_lon = (pm_dec * se_ca + pa_cd * (ce_cd + se_sd_sa)) / cl2 p_motion_lat = (pm_dec * (ce_cd + se_sd_sa) - pa_cd * se_ca) / c_lat return (p_motion_lon, p_motion_lat) def precession_newcomb( start_epoch, final_epoch, start_ra, start_dec, p_motion_ra=0.0, p_motion_dec=0.0 ): """This function implements the Newcomb precessional equations used in the old FK4 system. It takes equatorial coordinates (right ascension and declination) given for an epoch and a equinox, and converts them to the corresponding values for another epoch and equinox. Only the **mean** positions, i.e. the effects of precession and proper motion, are considered here. :param start_epoch: Initial epoch when initial coordinates are given :type start_epoch: :py:class:`Epoch` :param final_epoch: Final epoch for when coordinates are going to be computed :type final_epoch: :py:class:`Epoch` :param start_ra: Initial right ascension :type start_ra: :py:class:`Angle` :param start_dec: Initial declination :type start_dec: :py:class:`Angle` :param p_motion_ra: Proper motion in right ascension, in degrees per year. Zero by default. :type p_motion_ra: :py:class:`Angle` :param p_motion_dec: Proper motion in declination, in degrees per year. Zero by default. :type p_motion_dec: :py:class:`Angle` :returns: Equatorial coordinates (right ascension, declination, in that order) corresponding to the final epoch, given as two objects :class:`Angle` inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not ( isinstance(start_epoch, Epoch) and isinstance(final_epoch, Epoch) and isinstance(start_ra, Angle) and isinstance(start_dec, Angle) ): raise TypeError("Invalid input types") if isinstance(p_motion_ra, (int, float)): p_motion_ra = Angle(p_motion_ra) if isinstance(p_motion_dec, (int, float)): p_motion_dec = Angle(p_motion_dec) if not (isinstance(p_motion_ra, Angle) and isinstance(p_motion_dec, Angle)): raise TypeError("Invalid input types") tt = (start_epoch - 2415020.3135) / 36524.2199 t = (final_epoch - start_epoch) / 36524.2199 # Correct starting coordinates by proper motion start_ra += p_motion_ra * t * 100.0 start_dec += p_motion_dec * t * 100.0 # Compute the conversion parameters zeta = t * (2304.25 + 1.396 * tt + t * (0.302 + 0.018 * t)) z = zeta + t * t * (0.791 + 0.001 * t) theta = t * (2004.682 - 0.853 * tt - t * (0.426 + 0.042 * t)) # Redefine the former values as Angles zeta = Angle(0, 0, zeta) z = Angle(0, 0, z) theta = Angle(0, 0, theta) a = cos(start_dec.rad()) * sin(start_ra.rad() + zeta.rad()) b = cos(theta.rad()) * cos(start_dec.rad()) * cos( start_ra.rad() + zeta.rad() ) - sin(theta.rad()) * sin(start_dec.rad()) c = sin(theta.rad()) * cos(start_dec.rad()) * cos( start_ra.rad() + zeta.rad() ) + cos(theta.rad()) * sin(start_dec.rad()) final_ra = atan2(a, b) + z.rad() if start_dec > 85.0: # Coordinates are close to the pole final_dec = sqrt(a * a + b * b) else: final_dec = asin(c) # Convert results to Angles. Please note results are in radians final_ra = Angle(final_ra, radians=True) final_dec = Angle(final_dec, radians=True) return (final_ra, final_dec) def motion_in_space( start_ra, start_dec, distance, velocity, p_motion_ra, p_motion_dec, time ): """This function computes the star's true motion through space relative to the Sun, allowing to compute the start proper motion at a given time. :param start_ra: Initial right ascension :type start_ra: :py:class:`Angle` :param start_dec: Initial declination :type start_dec: :py:class:`Angle` :param distance: Star's distance to the Sun, in parsecs. If distance is given in light-years, multipy it by 0.3066. If the star's parallax **pie** (in arcseconds) is given, use (1.0/pie). :type distance: float :param velocity: Radial velocity in km/s :type velocity: float :param p_motion_ra: Proper motion in right ascension, in degrees per year. :type p_motion_ra: :py:class:`Angle` :param p_motion_dec: Proper motion in declination, in degrees per year. :type p_motion_dec: :py:class:`Angle` :param time: Number of years since starting epoch, positive in the future, negative in the past :type time: float :returns: Equatorial coordinates (right ascension, declination, in that order) corresponding to the final epoch, given as two objects :class:`Angle` inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> ra = Angle(6, 45, 8.871, ra=True) >>> dec = Angle(-16.716108) >>> pm_ra = Angle(0, 0, -0.03847, ra=True) >>> pm_dec = Angle(0, 0, -1.2053) >>> dist = 2.64 >>> vel = -7.6 >>> alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec, ... -1000.0) >>> print(alpha.ra_str(False, 2)) 6:45:47.16 >>> print(delta.dms_str(False, 1)) -16:22:56.0 >>> alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec, ... -4000.0) >>> print(alpha.ra_str(False, 2)) 6:47:39.91 >>> print(delta.dms_str(False, 1)) -15:23:30.6 """ # >>> ra = Angle(101.286962) # First check that input values are of correct types if not (isinstance(start_ra, Angle) and isinstance(start_dec, Angle)): raise TypeError("Invalid input types") if isinstance(p_motion_ra, (int, float)): p_motion_ra = Angle(p_motion_ra) if isinstance(p_motion_dec, (int, float)): p_motion_dec = Angle(p_motion_dec) if not (isinstance(p_motion_ra, Angle) and isinstance(p_motion_dec, Angle)): raise TypeError("Invalid input types") if not ( isinstance(distance, (int, float)) and isinstance(velocity, (int, float)) and isinstance(time, (int, float)) ): raise TypeError("Invalid input types") dr = velocity / 977792.0 x = distance * cos(start_dec.rad()) * cos(start_ra.rad()) y = distance * cos(start_dec.rad()) * sin(start_ra.rad()) z = distance * sin(start_dec.rad()) dx = ( (x / distance) * dr - z * p_motion_dec.rad() * cos(start_ra.rad()) - y * p_motion_ra.rad() ) dy = ( (y / distance) * dr - z * p_motion_dec.rad() * sin(start_ra.rad()) + x * p_motion_ra.rad() ) dz = ((z / distance) * dr + distance * p_motion_dec.rad() * cos(start_dec.rad())) xp = x + time * dx yp = y + time * dy zp = z + time * dz final_ra = atan2(yp, xp) final_dec = atan(zp / sqrt(xp * xp + yp * yp)) # Convert results to Angles. Please note results are in radians final_ra = Angle(final_ra, radians=True) final_dec = Angle(final_dec, radians=True) return (final_ra, final_dec) def equatorial2ecliptical(right_ascension, declination, obliquity): """This function converts from equatorial coordinated (right ascension and declination) to ecliptical coordinates (longitude and latitude). :param right_ascension: Right ascension, as an Angle object :type start_epoch: :py:class:`Angle` :param declination: Declination, as an Angle object :type start_epoch: :py:class:`Angle` :param obliquity: Obliquity of the ecliptic, as an Angle object :type obliquity: :py:class:`Angle` :returns: Ecliptical coordinates (longitude, latitude, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> ra = Angle(7, 45, 18.946, ra=True) >>> dec = Angle(28, 1, 34.26) >>> epsilon = Angle(23.4392911) >>> lon, lat = equatorial2ecliptical(ra, dec, epsilon) >>> print(round(lon(), 5)) 113.21563 >>> print(round(lat(), 5)) 6.68417 """ # First check that input values are of correct types if not ( isinstance(right_ascension, Angle) and isinstance(declination, Angle) and isinstance(obliquity, Angle) ): raise TypeError("Invalid input types") ra = right_ascension.rad() dec = declination.rad() eps = obliquity.rad() lon = atan2((sin(ra) * cos(eps) + tan(dec) * sin(eps)), cos(ra)) lat = asin(sin(dec) * cos(eps) - cos(dec) * sin(eps) * sin(ra)) lon = Angle(lon, radians=True) lon = lon.to_positive() lat = Angle(lat, radians=True) return (lon, lat) def ecliptical2equatorial(longitude, latitude, obliquity): """This function converts from ecliptical coordinates (longitude and latitude) to equatorial coordinated (right ascension and declination). :param longitude: Ecliptical longitude, as an Angle object :type longitude: :py:class:`Angle` :param latitude: Ecliptical latitude, as an Angle object :type latitude: :py:class:`Angle` :param obliquity: Obliquity of the ecliptic, as an Angle object :type obliquity: :py:class:`Angle` :returns: Equatorial coordinates (right ascension, declination, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> lon = Angle(113.21563) >>> lat = Angle(6.68417) >>> epsilon = Angle(23.4392911) >>> ra, dec = ecliptical2equatorial(lon, lat, epsilon) >>> print(ra.ra_str(n_dec=3)) 7h 45' 18.946'' >>> print(dec.dms_str(n_dec=2)) 28d 1' 34.26'' """ # First check that input values are of correct types if not ( isinstance(longitude, Angle) and isinstance(latitude, Angle) and isinstance(obliquity, Angle) ): raise TypeError("Invalid input types") lon = longitude.rad() lat = latitude.rad() eps = obliquity.rad() ra = atan2((sin(lon) * cos(eps) - tan(lat) * sin(eps)), cos(lon)) dec = asin(sin(lat) * cos(eps) + cos(lat) * sin(eps) * sin(lon)) ra = Angle(ra, radians=True) ra = ra.to_positive() dec = Angle(dec, radians=True) return (ra, dec) def equatorial2horizontal(hour_angle, declination, geo_latitude): """This function converts from equatorial coordinates (right ascension and declination) to local horizontal coordinates (azimuth and elevation). Following Meeus' convention, the azimuth is measured westward from the SOUTH. If you want the azimuth to be measured from the north (common custom between navigators and meteorologits), you should add 180 degrees. The hour angle (H) comprises information about the sidereal time, the observer's geodetic longitude (positive west from Greenwich) and the right ascension. If theta is the local sidereal time, theta0 the sidereal time at Greenwich, lon the observer's longitude and ra the right ascension, the following expressions hold: H = theta - ra H = theta0 - lon - ra :param hour_angle: Hour angle, as an Angle object :type hour_angle: :py:class:`Angle` :param declination: Declination, as an Angle object :type declination: :py:class:`Angle` :param geo_latitude: Geodetic latitude of the observer, as an Angle object :type geo_latitude: :py:class:`Angle` :returns: Local horizontal coordinates (azimuth, elevation, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> lon = Angle(77, 3, 56) >>> lat = Angle(38, 55, 17) >>> ra = Angle(23, 9, 16.641, ra=True) >>> dec = Angle(-6, 43, 11.61) >>> theta0 = Angle(8, 34, 57.0896, ra=True) >>> eps = Angle(23, 26, 36.87) >>> delta = Angle(0, 0, ((-3.868*cos(eps.rad()))/15.0), ra=True) >>> theta0 += delta >>> h = theta0 - lon - ra >>> azi, ele = equatorial2horizontal(h, dec, lat) >>> print(round(azi, 3)) 68.034 >>> print(round(ele, 3)) 15.125 """ # First check that input values are of correct types if not ( isinstance(hour_angle, Angle) and isinstance(declination, Angle) and isinstance(geo_latitude, Angle) ): raise TypeError("Invalid input types") h = hour_angle.rad() dec = declination.rad() lat = geo_latitude.rad() azi = atan2(sin(h), (cos(h) * sin(lat) - tan(dec) * cos(lat))) ele = asin(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h)) azi = Angle(azi, radians=True) ele = Angle(ele, radians=True) return (azi, ele) def horizontal2equatorial(azimuth, elevation, geo_latitude): """This function converts from local horizontal coordinates (azimuth and elevation) to equatorial coordinates (right ascension and declination). Following Meeus' convention, the azimuth is measured westward from the SOUTH. This function returns the hour angle and the declination. The hour angle (H) comprises information about the sidereal time, the observer's geodetic longitude (positive west from Greenwich) and the right ascension. If theta is the local sidereal time, theta0 the sidereal time at Greenwich, lon the observer's longitude and ra the right ascension, the following expressions hold: H = theta - ra H = theta0 - lon - ra :param azimuth: Azimuth, measured westward from south, as an Angle object :type azimuth: :py:class:`Angle` :param elevation: Elevation from the horizon, as an Angle object :type elevation: :py:class:`Angle` :param geo_latitude: Geodetic latitude of the observer, as an Angle object :type geo_latitude: :py:class:`Angle` :returns: Equatorial coordinates (as hour angle and declination, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> azi = Angle(68.0337) >>> ele = Angle(15.1249) >>> lat = Angle(38, 55, 17) >>> h, dec = horizontal2equatorial(azi, ele, lat) >>> print(round(h, 4)) 64.3521 >>> print(dec.dms_str(n_dec=0)) -6d 43' 12.0'' """ # First check that input values are of correct types if not ( isinstance(azimuth, Angle) and isinstance(elevation, Angle) and isinstance(geo_latitude, Angle) ): raise TypeError("Invalid input types") azi = azimuth.rad() ele = elevation.rad() lat = geo_latitude.rad() h = atan2(sin(azi), (cos(azi) * sin(lat) + tan(ele) * cos(lat))) dec = asin(sin(lat) * sin(ele) - cos(lat) * cos(ele) * cos(azi)) h = Angle(h, radians=True) dec = Angle(dec, radians=True) return (h, dec) def equatorial2galactic(right_ascension, declination): """This function converts from equatorial coordinates (right ascension and declination) to galactic coordinates (longitude and latitude). The current galactic system of coordinates was defined by the International Astronomical Union in 1959, using the standard equatorial system of epoch B1950.0. :param right_ascension: Right ascension, as an Angle object :type right_ascension: :py:class:`Angle` :param declination: Declination, as an Angle object :type declination: :py:class:`Angle` :returns: Galactic coordinates (longitude and latitude, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> ra = Angle(17, 48, 59.74, ra=True) >>> dec = Angle(-14, 43, 8.2) >>> lon, lat = equatorial2galactic(ra, dec) >>> print(round(lon, 4)) 12.9593 >>> print(round(lat, 4)) 6.0463 """ # First check that input values are of correct types if not (isinstance(right_ascension, Angle) and isinstance(declination, Angle)): raise TypeError("Invalid input types") ra = right_ascension.rad() dec = declination.rad() c1 = Angle(192.25) c1 = c1.rad() c1ra = c1 - ra c2 = Angle(27.4) c2 = c2.rad() x = atan2(sin(c1ra), (cos(c1ra) * sin(c2) - tan(dec) * cos(c2))) lon = Angle(-x, radians=True) lon = 303.0 + lon lon = lon.to_positive() lat = asin(sin(dec) * sin(c2) + cos(dec) * cos(c2) * cos(c1ra)) lat = Angle(lat, radians=True) return (lon, lat) def galactic2equatorial(longitude, latitude): """This function converts from galactic coordinates (longitude and latitude) to equatorial coordinates (right ascension and declination). The current galactic system of coordinates was defined by the International Astronomical Union in 1959, using the standard equatorial system of epoch B1950.0. :param longitude: Longitude, as an Angle object :type longitude: :py:class:`Angle` :param latitude: Latitude, as an Angle object :type latitude: :py:class:`Angle` :returns: Equatorial coordinates (right ascension and declination, in that order), given as two :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> lon = Angle(12.9593) >>> lat = Angle(6.0463) >>> ra, dec = galactic2equatorial(lon, lat) >>> print(ra.ra_str(n_dec=1)) 17h 48' 59.7'' >>> print(dec.dms_str(n_dec=0)) -14d 43' 8.0'' """ # First check that input values are of correct types if not (isinstance(longitude, Angle) and isinstance(latitude, Angle)): raise TypeError("Invalid input types") lon = longitude.rad() lat = latitude.rad() c1 = Angle(123.0) c1 = c1.rad() c2 = Angle(27.4) c2 = c2.rad() lc1 = lon - c1 y = atan2(sin(lc1), (cos(lc1) * sin(c2) - tan(lat) * cos(c2))) y = Angle(y, radians=True) ra = y + 12.25 ra.to_positive() dec = asin(sin(lat) * sin(c2) + cos(lat) * cos(c2) * cos(lc1)) dec = Angle(dec, radians=True) return (ra, dec) def parallactic_angle(hour_angle, declination, geo_latitude): """This function computes the parallactic angle, an apparent rotation that appears because celestial bodies move along parallel circles. By convention, the parallactic angle is negative before the passage through the southern meridian (in the north hemisphere), and positive afterwards. Exactly on the meridian, its value is zero. Please note that when the celestial body is exactly at the zenith, the parallactic angle is not defined, and this function will return 'None'. The hour angle (H) comprises information about the sidereal time, the observer's geodetic longitude (positive west from Greenwich) and the right ascension. If theta is the local sidereal time, theta0 the sidereal time at Greenwich, lon the observer's longitude and ra the right ascension, the following expressions hold: H = theta - ra H = theta0 - lon - ra :param hour_angle: Hour angle, as an Angle object :type hour_angle: :py:class:`Angle` :param declination: Declination, as an Angle object :type declination: :py:class:`Angle` :param geo_latitude: Geodetic latitude of the observer, as an Angle object :type geo_latitude: :py:class:`Angle` :returns: Parallactic angle as an py:class:`Angle` object :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> hour_angle = Angle(0.0) >>> declination = Angle(45.0) >>> latitude = Angle(50.0) >>> q = parallactic_angle(hour_angle, declination, latitude) >>> print(q.dms_str(n_dec=1)) 0d 0' 0.0'' """ # First check that input values are of correct types if not ( isinstance(hour_angle, Angle) and isinstance(declination, Angle) and isinstance(geo_latitude, Angle) ): raise TypeError("Invalid input types") h = hour_angle.rad() dec = declination.rad() lat = geo_latitude.rad() den = tan(lat) * cos(dec) - sin(dec) * cos(h) if abs(den) < TOL: return None q = atan2(sin(h), den) q = Angle(q, radians=True) return q def ecliptic_horizon(local_sidereal_time, geo_latitude, obliquity): """This function returns the longitudes of the two points of the ecliptic which are on the horizon, as well as the angle between the ecliptic and the horizon. :param local_sidereal_time: Local sidereal time, as an Angle object :type local_sidereal_time: :py:class:`Angle` :param geo_latitude: Geodetic latitude, as an Angle object :type geo_latitude: :py:class:`Angle` :param obliquity: Obliquity of the ecliptic, as an Angle object :type obliquity: :py:class:`Angle` :returns: Longitudes of the two points of the ecliptic which are on the horizon, and the angle between the ecliptic and the horizon (in that order), given as three :class:`Angle` objects inside a tuple :rtype: tuple :raises: TypeError if input values are of wrong type. >>> sidereal_time = Angle(5.0, ra=True) >>> lat = Angle(51.0) >>> epsilon = Angle(23.44) >>> lon1, lon2, i = ecliptic_horizon(sidereal_time, lat, epsilon) >>> print(lon1.dms_str(n_dec=1)) 169d 21' 29.9'' >>> print(lon2.dms_str(n_dec=1)) 349d 21' 29.9'' >>> print(round(i, 0)) 62.0 """ # First check that input values are of correct types if not ( isinstance(local_sidereal_time, Angle) and isinstance(geo_latitude, Angle) and isinstance(obliquity, Angle) ): raise TypeError("Invalid input types") theta = local_sidereal_time.rad() lat = geo_latitude.rad() eps = obliquity.rad() # First, let's compute the longitudes of the ecliptic points on the horizon lon1 = atan2(-cos(theta), (sin(eps) * tan(lat) + cos(eps) * sin(theta))) lon1 = Angle(lon1, radians=True) lon1.to_positive() # Get the second point, which is 180 degrees apart if lon1 < 180.0: lon2 = lon1 + 180.0 else: lon2 = lon1 lon1 = lon2 - 180.0 # Now, compute the angle between the ecliptic and the horizon i = acos(cos(eps) * sin(lat) - sin(eps) * cos(lat) * sin(theta)) i = Angle(i, radians=True) return (lon1, lon2, i) def ecliptic_equator(longitude, latitude, obliquity): """This function returns the angle between the direction of the northern celestial pole and the direction of the north pole of the ecliptic, taking as reference the point whose ecliptic longitude and latitude are given. Please note that if we make latitude=0, the result is the angle between the ecliptic (at the given ecliptical longitude) and the east-west direction on the celestial sphere. :param longitude: Ecliptical longitude, as an Angle object :type longitude: :py:class:`Angle` :param latitude: Ecliptical latitude, as an Angle object :type latitude: :py:class:`Angle` :param obliquity: Obliquity of the ecliptic, as an Angle object :type obliquity: :py:class:`Angle` :returns: Angle between the direction of the northern celestial pole and the direction of the north pole of the ecliptic, given as one :class:`Angle` object :rtype: :class:`Angle` :raises: TypeError if input values are of wrong type. >>> lon = Angle(0.0) >>> lat = Angle(0.0) >>> eps = Angle(23.5) >>> ang_ecl_equ = ecliptic_equator(lon, lat, eps) >>> print(ang_ecl_equ.dms_str(n_dec=1)) 156d 30' 0.0'' """ # First check that input values are of correct types if not ( isinstance(longitude, Angle) and isinstance(latitude, Angle) and isinstance(obliquity, Angle) ): raise TypeError("Invalid input types") lon = longitude.rad() lat = latitude.rad() eps = obliquity.rad() q = (atan2((cos(lon) * tan(eps)), (sin(lat) * sin(lon) * tan(eps) - cos(lat)))) q = Angle(q, radians=True) return q def diurnal_path_horizon(declination, geo_latitude): """This function returns the angle of the diurnal path of a celestial body relative to the horizon at the time of its rising or setting. :param declination: Declination, as an Angle object :type declination: :py:class:`Angle` :param geo_latitude: Geodetic latitude, as an Angle object :type geo_latitude: :py:class:`Angle` :returns: Angle of the diurnal path of the celestial body relative to the horizon at the time of rising or setting, given as one :class:`Angle` object :rtype: :class:`Angle` :raises: TypeError if input values are of wrong type. >>> declination = Angle(23.44) >>> latitude = Angle(40.0) >>> path_angle = diurnal_path_horizon(declination, latitude) >>> print(path_angle.dms_str(n_dec=1)) 45d 31' 28.4'' """ # First check that input values are of correct types if not (isinstance(declination, Angle) and isinstance(geo_latitude, Angle)): raise TypeError("Invalid input types") dec = declination.rad() lat = geo_latitude.rad() b = tan(dec) * tan(lat) c = sqrt(1.0 - b * b) j = atan2(c * cos(dec), tan(lat)) j = Angle(j, radians=True) return j def times_rise_transit_set( longitude, latitude, alpha1, delta1, alpha2, delta2, alpha3, delta3, h0, delta_t, theta0, ): """This function computes the times (in Universal Time UT) of rising, transit and setting of a given celestial body. .. note:: If the body is circumpolar there are no rising, transit nor setting times. In such a case a tuple with None's is returned .. note:: Care must be taken when interpreting the results. For instance, if the setting time is **smaller** than the rising time, it means that it belongs to the **following** day. Also, if the rising time is **bigger** than the setting time, it belong to the **previous** day. The same applies to the transit time. :param longitude: Geodetic longitude, as an Angle object. It is measured positively west from Greenwich, and negatively to the east. :type longitude: :py:class:`Angle` :param latitude: Geodetic latitude, as an Angle object :type latitude: :py:class:`Angle` :param alpha1: Apparent right ascension the previous day at 0h TT, as an Angle object :type alpha1: :py:class:`Angle` :param delta1: Apparent declination the previous day at 0h TT, as an Angle object :type delta1: :py:class:`Angle` :param alpha2: Apparent right ascension the current day at 0h TT, as an Angle object :type alpha2: :py:class:`Angle` :param delta2: Apparent declination the current day at 0h TT, as an Angle object :type delta2: :py:class:`Angle` :param alpha3: Apparent right ascension the following day at 0h TT, as an Angle object :type alpha3: :py:class:`Angle` :param delta3: Apparent declination the following day at 0h TT, as an Angle object :type delta3: :py:class:`Angle` :param h0: 'Standard' altitude: the geometric altitude of the center of the body at the time of apparent rising or setting, as degrees in an Angle object. It should be -0.5667 deg for stars and planets, -0.8333 deg for the Sun, and 0.125 deg for the Moon. :type h0: :py:class:`Angle` :param delta_t: The difference between Terrestrial Time and Universal Time (TT - UT) in seconds of time :type delta_t: float :param theta0: Apparent sidereal time at 0h TT on the current day for the meridian of Greenwich, as degrees in an Angle object :type theta0: :py:class:`Angle` :returns: A tuple with the times of rising, transit and setting, in that order, as hours in UT. :rtype: tuple :raises: TypeError if input values are of wrong type. >>> longitude = Angle(71, 5, 0.0) >>> latitude = Angle(42, 20, 0.0) >>> alpha1 = Angle(2, 42, 43.25, ra=True) >>> delta1 = Angle(18, 2, 51.4) >>> alpha2 = Angle(2, 46, 55.51, ra=True) >>> delta2 = Angle(18, 26, 27.3) >>> alpha3 = Angle(2, 51, 7.69, ra=True) >>> delta3 = Angle(18, 49, 38.7) >>> h0 = Angle(-0.5667) >>> delta_t = 56.0 >>> theta0 = Angle(11, 50, 58.1, ra=True) >>> rising, transit, setting = times_rise_transit_set(longitude, latitude,\ alpha1, delta1, \ alpha2, delta2, \ alpha3, delta3, h0, \ delta_t, theta0) >>> print(round(rising, 4)) 12.4238 >>> print(round(transit, 3)) 19.675 >>> print(round(setting, 3)) 2.911 """ def check_value(m): while m < 0 or m > 1.0: if m < 0.0: m += 1 elif m > 1.0: m -= 1 return m def interpol(n, y1, y2, y3): a = y2 - y1 b = y3 - y2 c = b - a return y2 + n * (a + b + n * c) / 2.0 # First check that input values are of correct types if not ( isinstance(longitude, Angle) and isinstance(latitude, Angle) and isinstance(alpha1, Angle) and isinstance(delta1, Angle) and isinstance(alpha2, Angle) and isinstance(delta2, Angle) and isinstance(alpha3, Angle) and isinstance(delta3, Angle) and isinstance(h0, Angle) and isinstance(theta0, Angle) and isinstance(delta_t, (int, float)) ): raise TypeError("Invalid input types") # Let's start computing approximate times h = h0.rad() lat = latitude.rad() d2 = delta2.rad() hh0 = (sin(h) - sin(lat) * sin(d2)) / (cos(lat) * cos(d2)) # Check if the body is circumpolar. In such case, there are no rising, # transit nor setting times, and a tuple with None's is returned if abs(hh0) > 1.0: return (None, None, None) hh0 = acos(hh0) hh0 = Angle(hh0, radians=True) hh0.to_positive() m0 = (alpha2 + longitude - theta0) / 360.0 m0 = m0() # m0 is an Angle. Convert to float m1 = m0 - hh0() / 360.0 m2 = m0 + hh0() / 360.0 m0 = check_value(m0) m1 = check_value(m1) m2 = check_value(m2) # Carry out this procedure twice for _ in range(2): # Interpolate alpha and delta values for each (m0, m1, m2) n = m0 + delta_t / 86400.0 transit_alpha = interpol(n, alpha1, alpha2, alpha3) n = m1 + delta_t / 86400.0 rise_alpha = interpol(n, alpha1, alpha2, alpha3) rise_delta = interpol(n, delta1, delta2, delta3) n = m2 + delta_t / 86400.0 set_alpha = interpol(n, alpha1, alpha2, alpha3) set_delta = interpol(n, delta1, delta2, delta3) # Compute the hour angles theta = theta0 + 360.985647 * m0 transit_ha = theta - longitude - transit_alpha delta_transit = transit_ha / (-360.0) theta = theta0 + 360.985647 * m1 rise_ha = theta - longitude - rise_alpha theta = theta0 + 360.985647 * m2 set_ha = theta - longitude - set_alpha # We need the elevations azi, rise_ele = equatorial2horizontal(rise_ha, rise_delta, latitude) azi, set_ele = equatorial2horizontal(set_ha, set_delta, latitude) delta_rise = (rise_ele - h0) / ( 360.0 * cos(rise_delta.rad()) * cos(lat) * sin(rise_ha.rad()) ) delta_set = (set_ele - h0) / ( 360.0 * cos(set_delta.rad()) * cos(lat) * sin(set_ha.rad()) ) m0 += delta_transit() m1 += delta_rise() m2 += delta_set() return (m1 * 24.0, m0 * 24.0, m2 * 24.0) def refraction_apparent2true(apparent_elevation, pressure=1010.0, temperature=10.0): """This function computes the atmospheric refraction converting from the apparent elevation (i.e., the observed elevation through the air) to the true, 'airless', elevation. .. note:: This function, by default, assumes that the atmospheric pressure is 1010 milibars, and the air temperature is 10 Celsius. .. note:: Due to the numerous factors that may affect the atmospheric refraction, especially near the horizon, the values given by this function are approximate values. :param apparent_elevation: The elevation, in degrees and as an Angle object, of a given celestial object when observed through the normal atmosphere :type apparent_elevation: :py:class:`Angle` :param pressure: Atmospheric pressure at the observation point, in milibars :type pressure: float :param temperature: Atmospheric temperature at the observation point, in degrees Celsius :type temperature: :float :returns: An Angle object with the true, 'airless' elevation of the celestial object :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> apparent_elevation = Angle(0, 30, 0.0) >>> true_elevation = refraction_apparent2true(apparent_elevation) >>> print(true_elevation.dms_str(n_dec=1)) 1' 14.7'' """ # First check that input values are of correct types if not ( isinstance(apparent_elevation, Angle) and isinstance(pressure, (int, float)) and isinstance(temperature, (int, float)) ): raise TypeError("Invalid input types") x = apparent_elevation + 7.31 / (apparent_elevation + 4.4) r = 1.0 / tan(x.rad()) + 0.0013515 r = Angle(r / 60.0) # The 'r' value is in minutes of arc if pressure != 1010.0 or temperature != 10.0: r = r * pressure / 1010.0 * 283.0 / (273.0 + temperature) return apparent_elevation - r def refraction_true2apparent(true_elevation, pressure=1010.0, temperature=10.0): """This function computes the atmospheric refraction converting from the true, 'airless', elevation (i.e., the one computed from celestial coordinates) to the apparent elevation (the observed elevation through the air) .. note:: This function, by default, assumes that the atmospheric pressure is 1010 milibars, and the air temperature is 10 Celsius. .. note:: Due to the numerous factors that may affect the atmospheric refraction, especially near the horizon, the values given by this function are approximate values. :param true_elevation: The elevation, in degrees and as an Angle object, of a given celestial object when computed from celestial coordinates, and assuming there is no atmospheric refraction due to the air :type true_elevation: :py:class:`Angle` :param pressure: Atmospheric pressure at the observation point, in milibars :type pressure: float :param temperature: Atmospheric temperature at the observation point, in degrees Celsius :type temperature: :float :returns: An Angle object with the aparent, 'with air' elevation of the celestial object :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> true_elevation = Angle(0, 33, 14.76) >>> apparent_elevation = refraction_true2apparent(true_elevation) >>> print(apparent_elevation.dms_str(n_dec=2)) 57' 51.96'' """ # First check that input values are of correct types if not ( isinstance(true_elevation, Angle) and isinstance(pressure, (int, float)) and isinstance(temperature, (int, float)) ): raise TypeError("Invalid input types") x = true_elevation + 10.3 / (true_elevation + 5.11) r = 1.02 / tan(x.rad()) + 0.0019279 r = Angle(r / 60.0) # The 'r' value is in minutes of arc if pressure != 1010.0 or temperature != 10.0: r = r * pressure / 1010.0 * 283.0 / (273.0 + temperature) return true_elevation + r def angular_separation(alpha1, delta1, alpha2, delta2): """This function computes the angular distance between two celestial bodies whose right ascensions and declinations are given. .. note:: It is possible to use this formula with ecliptial (celestial) longitudes and latitudes instead of right ascensions and declinations, respectively. :param alpha1: Right ascension of celestial body #1, as an Angle object :type alpha1: :py:class:`Angle` :param delta1: Declination of celestial body #1, as an Angle object :type delta1: :py:class:`Angle` :param alpha2: Right ascension of celestial body #2, as an Angle object :type alpha2: :py:class:`Angle` :param delta2: Declination of celestial body #2, as an Angle object :type delta2: :py:class:`Angle` :returns: An Angle object with the angular separation between the given celestial objects :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> alpha1 = Angle(14, 15, 39.7, ra=True) >>> delta1 = Angle(19, 10, 57.0) >>> alpha2 = Angle(13, 25, 11.6, ra=True) >>> delta2 = Angle(-11, 9, 41.0) >>> sep_ang = angular_separation(alpha1, delta1, alpha2, delta2) >>> print(round(sep_ang, 3)) 32.793 """ # Let's define an auxiliary function def hav(theta): """Function to compute the haversine (hav)""" return (1.0 - cos(theta)) / 2.0 # First check that input values are of correct types if not ( isinstance(alpha1, Angle) and isinstance(delta1, Angle) and isinstance(alpha2, Angle) and isinstance(delta2, Angle) ): raise TypeError("Invalid input types") dalpha = alpha1 - alpha2 dalpha = dalpha.rad() ddelta = delta1 - delta2 ddelta = ddelta.rad() d1 = delta1.rad() d2 = delta2.rad() theta = 2.0 * asin(sqrt(hav(ddelta) + cos(d1) * cos(d2) * hav(dalpha))) theta = Angle(theta, radians=True) return theta def minimum_angular_separation( alpha1_1, delta1_1, alpha1_2, delta1_2, alpha1_3, delta1_3, alpha2_1, delta2_1, alpha2_2, delta2_2, alpha2_3, delta2_3, ): """Given the positions at three different instants of times (equidistant) of two celestial objects, this function computes the minimum angular distance that will be achieved within that interval of time. .. note:: Suffix '1 _' is for the first celestial object, and '2 _' is for the second one. .. note:: This function provides as output the 'n' fraction of time when the minimum angular separation is achieved. For that, the epoch in the middle is assigned the value "n = 0". Therefore, n < 0 is for times **before** the middle epoch, and n > 0 is for times **after** the middle epoch. :param alpha1_1: First right ascension of celestial body #1, as an Angle object :type alpha1_1: :py:class:`Angle` :param delta1_1: First declination of celestial body #1, as an Angle object :type delta1_1: :py:class:`Angle` :param alpha1_2: Second right ascension of celestial body #1, as an Angle object :type alpha1_2: :py:class:`Angle` :param delta1_2: Second declination of celestial body #1, as Angle object :type delta1_2: :py:class:`Angle` :param alpha1_3: Third right ascension of celestial body #1, as an Angle object :type alpha1_3: :py:class:`Angle` :param delta1_3: Third declination of celestial body #1, as an Angle object :type delta1_3: :py:class:`Angle` :param alpha2_1: First right ascension of celestial body #2, as an Angle object :type alpha2_1: :py:class:`Angle` :param delta2_1: First declination of celestial body #2, as an Angle object :type delta2_1: :py:class:`Angle` :param alpha2_2: Second right ascension of celestial body #2, as an Angle object :type alpha2_2: :py:class:`Angle` :param delta2_2: Second declination of celestial body #2, as Angle object :type delta2_2: :py:class:`Angle` :param alpha2_3: Third right ascension of celestial body #2, as an Angle object :type alpha2_3: :py:class:`Angle` :param delta2_3: Third declination of celestial body #2, as an Angle object :type delta2_3: :py:class:`Angle` :returns: A tuple with two components: The first component is a float containing the 'n' fraction of time when the minimum angular separation is achieved. The second component is an Angle object containing the minimum angular separation between the given celestial objects :rtype: tuple :raises: TypeError if input values are of wrong type. >>> alpha1_1 = Angle(10, 29, 44.27, ra=True) >>> delta1_1 = Angle(11, 2, 5.9) >>> alpha2_1 = Angle(10, 33, 29.64, ra=True) >>> delta2_1 = Angle(10, 40, 13.2) >>> alpha1_2 = Angle(10, 36, 19.63, ra=True) >>> delta1_2 = Angle(10, 29, 51.7) >>> alpha2_2 = Angle(10, 33, 57.97, ra=True) >>> delta2_2 = Angle(10, 37, 33.4) >>> alpha1_3 = Angle(10, 43, 1.75, ra=True) >>> delta1_3 = Angle(9, 55, 16.7) >>> alpha2_3 = Angle(10, 34, 26.22, ra=True) >>> delta2_3 = Angle(10, 34, 53.9) >>> a = minimum_angular_separation(alpha1_1, delta1_1, alpha1_2, delta1_2,\ alpha1_3, delta1_3, alpha2_1, delta2_1,\ alpha2_2, delta2_2, alpha2_3, delta2_3) >>> print(round(a[0], 6)) -0.370726 >>> print(a[1].dms_str(n_dec=0)) 3' 44.0'' """ # Let's define some auxiliary functions def k_factor(d1, d_a): """This auxiliary function returns arcseconds, input is in radians""" return (206264.8062 / (1.0 + sin(d1) * sin(d1) * tan(d_a) * tan(d_a / 2.0))) def u_factor(k, d1, d_a, d_d): """Input is in radians, except for k (arcseconds)""" return -k * (1.0 - tan(d1) * sin(d_d)) * cos(d1) * tan(d_a) def v_factor(k, d1, d_a, d_d): """Input is in radians, except for k (arcseconds)""" return k * (sin(d_d) + sin(d1) * cos(d1) * tan(d_a) * tan(d_a / 2.0)) def u_prime(n, u1, u2, u3): return (u3 - u1) / 2.0 + n * (u1 + u3 - 2.0 * u2) def delta_n(u, u_p, v, v_p): return -(u * u_p + v * v_p) / (u_p * u_p + v_p * v_p) def interpol(n, y1, y2, y3): """This is formula 3.3 from Meeus book""" a = y2 - y1 b = y3 - y2 c = b - a return y2 + n * (a + b + n * c) / 2.0 # First check that input values are of correct types if not ( isinstance(alpha1_1, Angle) and isinstance(delta1_1, Angle) and isinstance(alpha1_2, Angle) and isinstance(delta1_2, Angle) and isinstance(alpha1_3, Angle) and isinstance(delta1_3, Angle) and isinstance(alpha2_1, Angle) and isinstance(delta2_1, Angle) and isinstance(alpha2_2, Angle) and isinstance(delta2_2, Angle) and isinstance(alpha2_3, Angle) and isinstance(delta2_3, Angle) ): raise TypeError("Invalid input types") # Let's define two dictionaries to store the intermediate results u = {} v = {} # Compute intermediate results for first epoch d1 = delta1_1.rad() d_a = alpha2_1.rad() - alpha1_1.rad() d_d = delta2_1.rad() - delta1_1.rad() k = k_factor(d1, d_a) u[1] = u_factor(k, d1, d_a, d_d) v[1] = v_factor(k, d1, d_a, d_d) # Compute intermediate results for second epoch d1 = delta1_2.rad() d_a = alpha2_2.rad() - alpha1_2.rad() d_d = delta2_2.rad() - delta1_2.rad() k = k_factor(d1, d_a) u[2] = u_factor(k, d1, d_a, d_d) v[2] = v_factor(k, d1, d_a, d_d) # Compute intermediate results for third epoch d1 = delta1_3.rad() d_a = alpha2_3.rad() - alpha1_3.rad() d_d = delta2_3.rad() - delta1_3.rad() k = k_factor(d1, d_a) u[3] = u_factor(k, d1, d_a, d_d) v[3] = v_factor(k, d1, d_a, d_d) # Iterate to find the solution n = 0.0 dn = 999999.9 while abs(dn) > 0.000001: uu = interpol(n, u[1], u[2], u[3]) vv = interpol(n, v[1], v[2], v[3]) up = u_prime(n, u[1], u[2], u[3]) vp = u_prime(n, v[1], v[2], v[3]) dn = delta_n(uu, up, vv, vp) n += dn # Let's compute the minimum distance, in arcseconds arcsec = sqrt(uu * uu + vv * vv) d = Angle(0, 0, arcsec) # Convert to an Angle object return n, d def relative_position_angle(alpha1, delta1, alpha2, delta2): """This function computes the position angle P of a body with respect to another body. :param alpha1: Right ascension of celestial body #1, as an Angle object :type alpha1: :py:class:`Angle` :param delta1: Declination of celestial body #1, as an Angle object :type delta1: :py:class:`Angle` :param alpha2: Right ascension of celestial body #2, as an Angle object :type alpha2: :py:class:`Angle` :param delta2: Declination of celestial body #2, as an Angle object :type delta2: :py:class:`Angle` :returns: An Angle object with the relative position angle between the given celestial objects :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> alpha1 = Angle(14, 15, 39.7, ra=True) >>> delta1 = Angle(19, 10, 57.0) >>> alpha2 = Angle(14, 15, 39.7, ra=True) >>> delta2 = Angle(-11, 9, 41.0) >>> pos_ang = relative_position_angle(alpha1, delta1, alpha2, delta2) >>> print(round(pos_ang, 1)) 0.0 """ # First check that input values are of correct types if not ( isinstance(alpha1, Angle) and isinstance(delta1, Angle) and isinstance(alpha2, Angle) and isinstance(delta2, Angle) ): raise TypeError("Invalid input types") da = alpha1 - alpha2 da = da.rad() d1 = delta1.rad() d2 = delta2.rad() p = atan2(sin(da), (cos(d2) * tan(d1) - sin(d2) * cos(da))) p = Angle(p, radians=True) return p def planetary_conjunction(alpha1_list, delta1_list, alpha2_list, delta2_list): """Given the positions of two planets passing near each other, this function computes the time of conjunction in right ascension, and the difference in declination of the two bodies at that time. .. note:: This function provides as output the 'n' fraction of time when the minimum angular separation is achieved. For that, the epoch in the middle is assigned the value "n = 0". Therefore, n < 0 is for times **before** the middle epoch, and n > 0 is for times **after** the middle epoch. .. note:: When the entries in the input values are more than three and even, the last entry is discarted and an odd number of entries will be used. :param alpha1_list: List (or tuple) containing the right ascensions (as Angle objects) for object #1 (minimum 3 entries) :type alpha1_list: list, tuple of :py:class:`Angle` :param delta1_list: List (or tuple) containing the declinations (as Angle objects) for object #1 (minimum 3 entries) :type delta1_list: list, tuple of :py:class:`Angle` :param alpha2_list: List (or tuple) containing the right ascensions (as Angle objects) for object #2 (minimum 3 entries) :type alpha2_list: list, tuple of :py:class:`Angle` :param delta2_list: List (or tuple) containing the declinations (as Angle objects) for object #2 (minimum 3 entries) :type delta2_list: list, tuple of :py:class:`Angle` :returns: A tuple with two components: The first component is a float containing the 'n' fraction of time when the conjunction occurs. The second component is an Angle object containing the declination separation between the given objects at conjunction epoch :rtype: tuple :raises: ValueError if input values have less than three entries or they don't have the same number of entries. :raises: TypeError if input values are of wrong type. >>> alpha1_1 = Angle(10, 24, 30.125, ra=True) >>> delta1_1 = Angle( 6, 26, 32.05) >>> alpha1_2 = Angle(10, 25, 0.342, ra=True) >>> delta1_2 = Angle( 6, 10, 57.72) >>> alpha1_3 = Angle(10, 25, 12.515, ra=True) >>> delta1_3 = Angle( 5, 57, 33.08) >>> alpha1_4 = Angle(10, 25, 6.235, ra=True) >>> delta1_4 = Angle( 5, 46, 27.07) >>> alpha1_5 = Angle(10, 24, 41.185, ra=True) >>> delta1_5 = Angle( 5, 37, 48.45) >>> alpha2_1 = Angle(10, 27, 27.175, ra=True) >>> delta2_1 = Angle( 4, 4, 41.83) >>> alpha2_2 = Angle(10, 26, 32.410, ra=True) >>> delta2_2 = Angle( 3, 55, 54.66) >>> alpha2_3 = Angle(10, 25, 29.042, ra=True) >>> delta2_3 = Angle( 3, 48, 3.51) >>> alpha2_4 = Angle(10, 24, 17.191, ra=True) >>> delta2_4 = Angle( 3, 41, 10.25) >>> alpha2_5 = Angle(10, 22, 57.024, ra=True) >>> delta2_5 = Angle( 3, 35, 16.61) >>> alpha1_list = [alpha1_1, alpha1_2, alpha1_3, alpha1_4, alpha1_5] >>> delta1_list = [delta1_1, delta1_2, delta1_3, delta1_4, delta1_5] >>> alpha2_list = [alpha2_1, alpha2_2, alpha2_3, alpha2_4, alpha2_5] >>> delta2_list = [delta2_1, delta2_2, delta2_3, delta2_4, delta2_5] >>> pc = planetary_conjunction(alpha1_list, delta1_list, \ alpha2_list, delta2_list) >>> print(round(pc[0], 5)) 0.23797 >>> print(pc[1].dms_str(n_dec=1)) 2d 8' 21.8'' """ # First check that input values are of correct types if not ( isinstance(alpha1_list, (list, tuple)) and isinstance(delta1_list, (list, tuple)) and isinstance(alpha2_list, (list, tuple)) and isinstance(delta2_list, (list, tuple)) ): raise TypeError("Invalid input types") if ( len(alpha1_list) < 3 or len(delta1_list) < 3 or len(alpha2_list) < 3 or len(delta2_list) < 3 ): raise ValueError("Invalid number of entries") if ( len(alpha1_list) != len(delta1_list) or len(alpha1_list) != len(alpha2_list) or len(alpha1_list) != len(delta2_list) ): raise ValueError("Uneven number of entries") n_entries = len(alpha1_list) if n_entries % 2 != 1: # Check if number of entries is odd alpha1_list = alpha1_list[:-1] # Drop the last entry delta1_list = delta1_list[:-1] alpha2_list = alpha2_list[:-1] delta2_list = delta2_list[:-1] n_entries = len(alpha1_list) half_entries = n_entries // 2 # Compute the list with the time ('n') entries n_list = [i - half_entries for i in range(n_entries)] # Compute lists with differences between right ascensions and declinations # for objects #1 and #2 dalpha = [alpha1_list[i] - alpha2_list[i] for i in range(n_entries)] ddelta = [delta1_list[i] - delta2_list[i] for i in range(n_entries)] # Build the interpolation objects i_alpha = Interpolation(n_list, dalpha) i_delta = Interpolation(n_list, ddelta) # Find when the dalphas are 0 (i.e., the 'root') n_0 = i_alpha.root() # Now, let's find the declination difference with the newly found 'n_0' dd = i_delta(n_0) # We are done, let's return return n_0, dd def planet_star_conjunction(alpha_list, delta_list, alpha_star, delta_star): """Given the positions of one planet passing near a star, this function computes the time of conjunction in right ascension, and the difference in declination of the two bodies at that time. .. note:: This function provides as output the 'n' fraction of time when the minimum angular separation is achieved. For that, the epoch in the middle is assigned the value "n = 0". Therefore, n < 0 is for times **before** the middle epoch, and n > 0 is for times **after** the middle epoch. .. note:: When the entries in the input values for the planet are more than three and pair, the last entry is discarted and an odd number of entries will be used. :param alpha_list: List (or tuple) containing the right ascensions (as Angle objects) for the planet (minimum 3 entries) :type alpha_list: list, tuple of :py:class:`Angle` :param delta_list: List (or tuple) containing the declinations (as Angle objects) for the planet (minimum 3 entries) :type delta_list: list, tuple of :py:class:`Angle` :param alpha_star: Right ascension, as an Angle object, of the star :type alpha_star: :py:class:`Angle` :param delta_star: Declination, as an Angle object, of the star :type delta_star: :py:class:`Angle` :returns: A tuple with two components: The first component is a float containing the 'n' fraction of time when the conjunction occurs. The second component is an Angle object containing the declination separation between the given objects at conjunction epoch :rtype: tuple :raises: ValueError if input values for planet have less than three entries or they don't have the same number of entries. :raises: TypeError if input values are of wrong type. >>> alpha_1 = Angle(15, 3, 51.937, ra=True) >>> delta_1 = Angle(-8, 57, 34.51) >>> alpha_2 = Angle(15, 9, 57.327, ra=True) >>> delta_2 = Angle(-9, 9, 3.88) >>> alpha_3 = Angle(15, 15, 37.898, ra=True) >>> delta_3 = Angle(-9, 17, 37.94) >>> alpha_4 = Angle(15, 20, 50.632, ra=True) >>> delta_4 = Angle(-9, 23, 16.25) >>> alpha_5 = Angle(15, 25, 32.695, ra=True) >>> delta_5 = Angle(-9, 26, 1.01) >>> alpha_star = Angle(15, 17, 0.446, ra=True) >>> delta_star = Angle(-9, 22, 58.47) >>> alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5] >>> delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5] >>> pc = planet_star_conjunction(alpha_list, delta_list, \ alpha_star, delta_star) >>> print(round(pc[0], 4)) 0.2551 >>> print(pc[1].dms_str(n_dec=0)) 3' 38.0'' """ # Build the corresponding lists for the star n_entries = len(alpha_list) alpha_star_list = [alpha_star for _ in range(n_entries)] delta_star_list = [delta_star for _ in range(n_entries)] # Call the 'planetary_conjunction()' function. It handles everything else return planetary_conjunction( alpha_list, delta_list, alpha_star_list, delta_star_list ) def planet_stars_in_line( alpha_list, delta_list, alpha_star1, delta_star1, alpha_star2, delta_star2 ): """Given the positions of one planet, this function computes the time when it is in a straight line with two other stars. .. note:: This function provides as output the 'n' fraction of time when the minimum angular separation is achieved. For that, the epoch in the middle is assigned the value "n = 0". Therefore, n < 0 is for times **before** the middle epoch, and n > 0 is for times **after** the middle epoch. .. note:: When the entries in the input values for the planet are more than three and pair, the last entry is discarted and an odd number of entries will be used. :param alpha_list: List (or tuple) containing the right ascensions (as Angle objects) for the planet (minimum 3 entries) :type alpha_list: list, tuple of :py:class:`Angle` :param delta_list: List (or tuple) containing the declinations (as Angle objects) for the planet (minimum 3 entries) :type delta_list: list, tuple of :py:class:`Angle` :param alpha_star1: Right ascension, as an Angle object, of star #1 :type alpha_star1: :py:class:`Angle` :param delta_star1: Declination, as an Angle object, of star #1 :type delta_star1: :py:class:`Angle` :param alpha_star2: Right ascension, as an Angle object, of star #2 :type alpha_star2: :py:class:`Angle` :param delta_star2: Declination, as an Angle object, of star #2 :type delta_star2: :py:class:`Angle` :returns: A float containing the 'n' fraction of time when the alignment occurs. :rtype: float :raises: ValueError if input values for planet have less than three entries or they don't have the same number of entries. :raises: TypeError if input values are of wrong type. >>> alpha_1 = Angle( 7, 55, 55.36, ra=True) >>> delta_1 = Angle(21, 41, 3.0) >>> alpha_2 = Angle( 7, 58, 22.55, ra=True) >>> delta_2 = Angle(21, 35, 23.4) >>> alpha_3 = Angle( 8, 0, 48.99, ra=True) >>> delta_3 = Angle(21, 29, 38.2) >>> alpha_4 = Angle( 8, 3, 14.66, ra=True) >>> delta_4 = Angle(21, 23, 47.5) >>> alpha_5 = Angle( 8, 5, 39.54, ra=True) >>> delta_5 = Angle(21, 17, 51.4) >>> alpha_star1 = Angle( 7, 34, 16.40, ra=True) >>> delta_star1 = Angle(31, 53, 51.2) >>> alpha_star2 = Angle( 7, 45, 0.10, ra=True) >>> delta_star2 = Angle(28, 2, 12.5) >>> alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5] >>> delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5] >>> n = planet_stars_in_line(alpha_list, delta_list, alpha_star1, \ delta_star1, alpha_star2, delta_star2) >>> print(round(n, 4)) 0.2233 """ # Define an auxiliary function def straight(alpha1, delta1, alpha2, delta2, alpha3, delta3): a1 = alpha1.rad() d1 = delta1.rad() a2 = alpha2.rad() d2 = delta2.rad() a3 = alpha3.rad() d3 = delta3.rad() return (tan(d1) * sin(a2 - a3) + tan(d2) * sin(a3 - a1) + tan(d3) * sin(a1 - a2)) # First check that input values are of correct types if not ( isinstance(alpha_list, (list, tuple)) and isinstance(delta_list, (list, tuple)) and isinstance(alpha_star1, Angle) and isinstance(delta_star1, Angle) and isinstance(alpha_star2, Angle) and isinstance(delta_star2, Angle) ): raise TypeError("Invalid input types") if len(alpha_list) < 3 or len(delta_list) < 3: raise ValueError("Invalid number of entries") if len(alpha_list) != len(delta_list): raise ValueError("Uneven number of entries") n_entries = len(alpha_list) if n_entries % 2 != 1: # Check if number of entries is odd alpha_list = alpha_list[:-1] # Drop the last entry delta_list = delta_list[:-1] n_entries = len(alpha_list) half_entries = n_entries // 2 # Compute the list with the time ('n') entries n_list = [i - half_entries for i in range(n_entries)] # Use auxiliary function 'straight()' to compute the values to interpolate dx = [ straight( alpha_list[i], delta_list[i], alpha_star1, delta_star1, alpha_star2, delta_star2, ) for i in range(n_entries) ] # Build the interpolation objects i = Interpolation(n_list, dx) # Find when the dx's are 0 (i.e., the 'root') n_0 = i.root() return n_0 def straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3): """This function computes if three celestial bodies are in a straight line, providing the angle with which the bodies differ from a great circle. :param alpha1: Right ascension, as an Angle object, of celestial body #1 :type alpha1: :py:class:`Angle` :param delta1: Declination, as an Angle object, of celestial body #1 :type delta1: :py:class:`Angle` :param alpha2: Right ascension, as an Angle object, of celestial body #2 :type alpha2: :py:class:`Angle` :param delta2: Declination, as an Angle object, of celestial body #2 :type delta2: :py:class:`Angle` :param alpha3: Right ascension, as an Angle object, of celestial body #3 :type alpha3: :py:class:`Angle` :param delta3: Declination, as an Angle object, of celestial body #3 :type delta3: :py:class:`Angle` :returns: A tuple with two components. The first element is an angle (as Angle object) with which the bodies differ from a great circle. The second element is the Angular distance of central point to the straight line (also as Angle object). :rtype: tuple :raises: TypeError if input values are of wrong type. >>> alpha1 = Angle( 5, 32, 0.40, ra=True) >>> delta1 = Angle(0, -17, 56.9) >>> alpha2 = Angle( 5, 36, 12.81, ra=True) >>> delta2 = Angle(-1, 12, 7.0) >>> alpha3 = Angle( 5, 40, 45.52, ra=True) >>> delta3 = Angle(-1, 56, 33.3) >>> psi, om = straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3) >>> print(psi.dms_str(n_dec=0)) 7d 31' 1.0'' >>> print(om.dms_str(n_dec=0)) -5' 24.0'' """ # First check that input values are of correct types if not ( isinstance(alpha1, Angle) and isinstance(delta1, Angle) and isinstance(alpha2, Angle) and isinstance(delta2, Angle) and isinstance(alpha3, Angle) and isinstance(delta3, Angle) ): raise TypeError("Invalid input types") # We need to order the input according to right ascension a = [alpha1.rad(), alpha2.rad(), alpha3.rad()] d = [delta1.rad(), delta2.rad(), delta3.rad()] anew = [] dnew = [] amax = max(a) + 1.0 for _ in range(len(a)): # Get the index of the minimum value imin = a.index(min(a)) # Append the current minimum value to the new 'a' list anew.append(a[imin]) # Store the *position* of the current minimum value to new 'd' list dnew.append(imin) # The current minimum value will no longer be the minimum a[imin] = amax # In the new 'd' list, substitute the positions by the real values for i in range(len(a)): dnew[i] = d[dnew[i]] # Substitute the new values in the original list a = anew d = dnew # Compute the parameters a1 = cos(d[0]) * cos(a[0]) a2 = cos(d[1]) * cos(a[1]) a3 = cos(d[2]) * cos(a[2]) b1 = cos(d[0]) * sin(a[0]) b2 = cos(d[1]) * sin(a[1]) b3 = cos(d[2]) * sin(a[2]) c1 = sin(d[0]) c2 = sin(d[1]) c3 = sin(d[2]) l1 = b1 * c2 - b2 * c1 l2 = b2 * c3 - b3 * c2 l3 = b1 * c3 - b3 * c1 m1 = c1 * a2 - c2 * a1 m2 = c2 * a3 - c3 * a2 m3 = c1 * a3 - c3 * a1 n1 = a1 * b2 - a2 * b1 n2 = a2 * b3 - a3 * b2 n3 = a1 * b3 - a3 * b1 psi = acos( (l1 * l2 + m1 * m2 + n1 * n2) / (sqrt(l1 * l1 + m1 * m1 + n1 * n1) * sqrt(l2 * l2 + m2 * m2 + n2 * n2))) omega = asin( (a2 * l3 + b2 * m3 + c2 * n3) / (sqrt(a2 * a2 + b2 * b2 + c2 * c2) * sqrt(l3 * l3 + m3 * m3 + n3 * n3))) return Angle(psi, radians=True), Angle(omega, radians=True) def circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3): """This function computes the diameter of the smallest circle that contains three celestial bodies. :param alpha1: Right ascension, as an Angle object, of celestial body #1 :type alpha1: :py:class:`Angle` :param delta1: Declination, as an Angle object, of celestial body #1 :type delta1: :py:class:`Angle` :param alpha2: Right ascension, as an Angle object, of celestial body #2 :type alpha2: :py:class:`Angle` :param delta2: Declination, as an Angle object, of celestial body #2 :type delta2: :py:class:`Angle` :param alpha3: Right ascension, as an Angle object, of celestial body #3 :type alpha3: :py:class:`Angle` :param delta3: Declination, as an Angle object, of celestial body #3 :type delta3: :py:class:`Angle` :returns: The diameter (as an Angle object) of the smallest circle containing the three bodies. :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> alpha1 = Angle(12, 41, 8.63, ra=True) >>> delta1 = Angle(-5, 37, 54.2) >>> alpha2 = Angle(12, 52, 5.21, ra=True) >>> delta2 = Angle(-4, 22, 26.2) >>> alpha3 = Angle(12, 39, 28.11, ra=True) >>> delta3 = Angle(-1, 50, 3.7) >>> d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3) >>> print(d.dms_str(n_dec=0)) 4d 15' 49.0'' >>> alpha1 = Angle(9, 5, 41.44, ra=True) >>> delta1 = Angle(18, 30, 30.0) >>> alpha2 = Angle(9, 9, 29.0, ra=True) >>> delta2 = Angle(17, 43, 56.7) >>> alpha3 = Angle(8, 59, 47.14, ra=True) >>> delta3 = Angle(17, 49, 36.8) >>> d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3) >>> print(d.dms_str(n_dec=0)) 2d 18' 38.0'' """ # First check that input values are of correct types if not ( isinstance(alpha1, Angle) and isinstance(delta1, Angle) and isinstance(alpha2, Angle) and isinstance(delta2, Angle) and isinstance(alpha3, Angle) and isinstance(delta3, Angle) ): raise TypeError("Invalid input types") d12 = angular_separation(alpha1, delta1, alpha2, delta2) d13 = angular_separation(alpha1, delta1, alpha3, delta3) d23 = angular_separation(alpha2, delta2, alpha3, delta3) if d12 >= d13 and d12 >= d23: a = d12() b = d13() c = d23() elif d13 >= d12 and d13 >= d23: a = d13() b = d12() c = d23() else: a = d23() b = d12() c = d13() if a >= sqrt(b * b + c * c): d = a else: d = (2.0 * a * b * c) / sqrt( (a + b + c) * (a + b - c) * (b + c - a) * (a + c - b) ) return Angle(d) def vsop_pos(epoch, vsop_l, vsop_b, vsop_r): """This function computes the position of a celestial body at a given epoch when its VSOP87 periodic term tables are provided. :param epoch: Epoch to compute the position, given as an :class:`Epoch` object :type epoch: :py:class:`Epoch` :param vsop_l: Table of VSOP87 terms for the heliocentric longitude :type vsop_l: list :param vsop_b: Table of VSOP87 terms for the heliocentric latitude :type vsop_b: list :param vsop_r: Table of VSOP87 terms for the radius vector :type vsop_r: list :returns: A tuple with the heliocentric longitude and latitude (as :py:class:`Angle` objects), and the radius vector (as a float, in astronomical units), in that order :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not ( isinstance(epoch, Epoch) and isinstance(vsop_l, list) and isinstance(vsop_b, list) and isinstance(vsop_r, list) ): raise TypeError("Invalid input types") # Let's redefine u in units of 100 Julian centuries from Epoch J2000.0 t = (epoch.jde() - 2451545.0) / 365250.0 sum_list = [] for i in range(len(vsop_l)): s = 0.0 for k in range(len(vsop_l[i])): s += vsop_l[i][k][0] * cos(vsop_l[i][k][1] + vsop_l[i][k][2] * t) sum_list.append(s) lon = 0.0 # Sum the longitude terms, while multiplying by 't' at the same time for i in range(len(sum_list) - 1, 0, -1): lon = (lon + sum_list[i]) * t # Add the L0 term, which is NOT multiplied by 't' lon += sum_list[0] lon /= 1e8 lon = Angle(lon, radians=True) lon = lon.to_positive() sum_list = [] for i in range(len(vsop_b)): s = 0.0 for k in range(len(vsop_b[i])): s += vsop_b[i][k][0] * cos(vsop_b[i][k][1] + vsop_b[i][k][2] * t) sum_list.append(s) lat = 0.0 # Sum the latitude terms, while multiplying by 't' at the same time for i in range(len(sum_list) - 1, 0, -1): lat = (lat + sum_list[i]) * t # Add the B0 term, which is NOT multiplied by 't' lat += sum_list[0] lat /= 1e8 lat = Angle(lat, radians=True) sum_list = [] for i in range(len(vsop_r)): s = 0.0 for k in range(len(vsop_r[i])): s += vsop_r[i][k][0] * cos(vsop_r[i][k][1] + vsop_r[i][k][2] * t) sum_list.append(s) r = 0.0 # Sum the radius vector terms, while multiplying by 't' at the same time for i in range(len(sum_list) - 1, 0, -1): r = (r + sum_list[i]) * t # Add the R0 term, which is NOT multiplied by 't' r += sum_list[0] r /= 1e8 return (lon, lat, r) def geometric_vsop_pos(epoch, vsop_l, vsop_b, vsop_r, tofk5=True): """This function computes the geometric position of a celestial body at a given epoch when its VSOP87 periodic term tables are provided. The small correction to convert to the FK5 system may or not be included. :param epoch: Epoch to compute the position, given as an :class:`Epoch` object :type epoch: :py:class:`Epoch` :param vsop_l: Table of VSOP87 terms for the heliocentric longitude :type vsop_l: list :param vsop_b: Table of VSOP87 terms for the heliocentric latitude :type vsop_b: list :param vsop_r: Table of VSOP87 terms for the radius vector :type vsop_r: list :param tofk5: Whether or not the small correction to convert to the FK5 system will be applied :type tofk5: bool :returns: A tuple with the geometric heliocentric longitude and latitude (as :py:class:`Angle` objects), and the radius vector (as a float, in astronomical units), in that order :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not isinstance(epoch, Epoch): raise TypeError("Invalid input types") # Second, call the auxiliary function in charge of computations lon, lat, r = vsop_pos(epoch, vsop_l, vsop_b, vsop_r) if tofk5: # Apply the small correction for conversion to the FK5 system t = (epoch.jde() - 2451545.0) / 36525.0 lambda_p = lon - t * (1.397 + 0.00031 * t) delta_lon = Angle(0, 0, -0.09033) a = 0.03916 * (cos(lambda_p.rad()) + sin(lambda_p.rad())) a = a * tan(lat.rad()) delta_lon += Angle(0, 0, a) delta_beta = 0.03916 * (cos(lambda_p.rad()) - sin(lambda_p.rad())) delta_beta = Angle(0, 0, delta_beta) lon += delta_lon lat += delta_beta return lon, lat, r def apparent_vsop_pos(epoch, vsop_l, vsop_b, vsop_r, nutation=True): """This function computes the apparent position of a celestial body at a given epoch when its VSOP87 periodic term tables are provided. The small correction to convert to the FK5 system is always included. :param epoch: Epoch to compute the position, given as an :class:`Epoch` object :type epoch: :py:class:`Epoch` :param vsop_l: Table of VSOP87 terms for the heliocentric longitude :type vsop_l: list :param vsop_b: Table of VSOP87 terms for the heliocentric latitude :type vsop_b: list :param vsop_r: Table of VSOP87 terms for the radius vector :type vsop_r: list :param nutation: Whether the nutation correction will be applied :type tofk5: bool :returns: A tuple with the geometric heliocentric longitude and latitude (as :py:class:`Angle` objects), and the radius vector (as a float, in astronomical units), in that order :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not isinstance(epoch, Epoch): raise TypeError("Invalid input types") # Second, call auxiliary function in charge of computations lon, lat, r = geometric_vsop_pos(epoch, vsop_l, vsop_b, vsop_r) if nutation: lon += nutation_longitude(epoch) delta = -20.4898 / r delta = Angle(0, 0, delta) lon += delta return lon, lat, r def apparent_position(epoch, alpha, delta, sun_lon): """This function computes the apparent position of a star, correcting by nutation and aberration effects. :param epoch: Epoch to compute the apparent position for :type epoch: :py:class:`Epoch` :param alpha: Right ascension of the star, as an Angle object :type alpha: :py:class:`Angle` :param delta: Declination of the star, as an Angle object :type delta: :py:class:`Angle` :param sun_lon: True (geometric) longitude of the Sun :type sun_lon: :py:class:`Angle` :returns: A tuple with two Angle objects: Apparent right ascension, and aparent declination :rtype: tuple :raises: TypeError if input values are of wrong type. >>> epoch = Epoch(2028, 11, 13.19) >>> alpha = Angle(2, 46, 11.331, ra=True) >>> delta = Angle(49, 20, 54.54) >>> sun_lon = Angle(231.328) >>> app_alpha, app_delta = apparent_position(epoch, alpha, delta, sun_lon) >>> print(app_alpha.ra_str(n_dec=2)) 2h 46' 14.39'' >>> print(app_delta.dms_str(n_dec=2)) 49d 21' 7.45'' """ # First check that input values are of correct types if not ( isinstance(epoch, Epoch) and isinstance(alpha, Angle) and isinstance(delta, Angle) and isinstance(sun_lon, Angle) ): raise TypeError("Invalid input types") # Proceed to compute the true obliquity, nutation in longitude and nutation # in obliquity epsilon = true_obliquity(epoch) dpsi = nutation_longitude(epoch) depsilon = nutation_obliquity(epoch) # Convert the angles to radians a = alpha.rad() d = delta.rad() eps = epsilon.rad() # Compute corrections due to nutation dalpha1 = ((cos(eps) + sin(eps) * sin(a) * tan(d)) * dpsi - (cos(a) * tan(d)) * depsilon) ddelta1 = (sin(eps) * cos(a)) * dpsi + sin(a) * depsilon dalpha1 = Angle(dalpha1) ddelta1 = Angle(ddelta1) # Now, let's compute the aberration effect t = (epoch - JDE2000) / 36525 e = 0.016708634 + t * (-0.000042037 - t * 0.0000001267) pie = 102.93735 + t * (1.71946 + t * 0.00046) pie = radians(pie) lon = sun_lon.rad() k = 20.49552 # The constant of aberration dalpha2 = k * (-(cos(a) * cos(lon) * cos(eps) + sin(a) * sin(lon)) / cos(d) + e * (cos(a) * cos(pie) * cos(eps) + sin(a) * sin(pie)) / cos(d)) ddelta2 = k * (-(cos(lon) * cos(eps) * (tan(eps) * cos(d) - sin(a) * sin(d)) + cos(a) * sin(d) * sin(lon)) + e * (cos(pie) * cos(eps) * (tan(eps) * cos(d) - sin(a) * sin(d)) + cos(a) * sin(d) * sin(pie))) dalpha2 = Angle(0, 0, dalpha2) ddelta2 = Angle(0, 0, ddelta2) # Add the two corrections to the original values r_alpha = alpha + dalpha1 + dalpha2 r_delta = delta + ddelta1 + ddelta2 return r_alpha, r_delta def orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0): """This function reduces the orbital elements of a celestial object from one equinox to another. :param epoch0: Initial epoch :type epoch0: :py:class:`Epoch` :param epoch: Final epoch :type epoch: :py:class:`Epoch` :param i0: Initial inclination, as an Angle object :type i0: :py:class:`Angle` :param arg0: Initial argument of perihelion, as an Angle object :type arg0: :py:class:`Angle` :param lon0: Initial longitude of ascending node, as an Angle object :type lon0: :py:class:`Angle` :returns: A tuple with three Angle objects: Final inclination, argument of perihelion and longitude of ascending node, in that order :rtype: tuple :raises: TypeError if input values are of wrong type. >>> epoch0 = Epoch(2358042.5305) >>> epoch = Epoch(2433282.4235) >>> i0 = Angle(47.122) >>> arg0 = Angle(151.4486) >>> lon0 = Angle(45.7481) >>> i1, arg1, lon1 = orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0) >>> print(round(i1(), 3)) 47.138 >>> print(round(arg1(), 4)) 151.4782 >>> print(round(lon1(), 4)) 48.6037 """ # First check that input values are of correct types if not ( isinstance(epoch0, Epoch) and isinstance(epoch, Epoch) and isinstance(i0, Angle) and isinstance(arg0, Angle) and isinstance(lon0, Angle) ): raise TypeError("Invalid input types") # Compute the auxiliary angles tt = (epoch0 - JDE2000) / 36525.0 t = (epoch - epoch0) / 36525.0 # Compute the conversion parameters eta = t * ( (47.0029 + tt * (-0.06603 + 0.000598 * tt)) + t * ((-0.03302 + 0.000598 * tt) + 0.00006 * t) ) pie = tt * (3289.4789 + 0.60622 * tt) + t * ( -(869.8089 + 0.50491 * tt) + 0.03536 * t ) p = t * ( 5029.0966 + tt * (2.22226 - 0.000042 * tt) + t * (1.11113 - 0.000042 * tt - 0.000006 * t) ) eta = Angle(0, 0, eta) pie = Angle(0, 0, pie) # But beware!: There is still a missing constant for pie. We didn't add # it before because of the mismatch between degrees and seconds pie += 174.876384 p = Angle(0, 0, p) i0r = i0.rad() etar = eta.rad() lon0r = lon0.rad() pir = pie.rad() # If i0 is very small, the procedure is different if i0 < 1.0: i1 = eta lon1 = pie + p + 180.0 else: a = sin(i0r) * sin(lon0r - pir) b = -sin(etar) * cos(i0r) + cos(etar) * sin(i0r) * cos(lon0r - pir) i1 = asin(sqrt(a*a + b*b)) i1 = Angle(i1, radians=True) omegapsi = atan2(a, b) omegapsi = Angle(omegapsi, radians=True) lon1 = omegapsi + pie + p domega = atan2(-sin(etar) * sin(lon0r - pir), sin(i0r) * cos(etar) - cos(i0r) * sin(etar) * cos(lon0r - pir)) domega = Angle(domega, radians=True) arg1 = arg0 + domega return i1, arg1, lon1 def kepler_equation(eccentricity, mean_anomaly): """This function computes the eccentric and true anomalies taking as input the mean anomaly and the eccentricity. :param eccentricity: Orbit's eccentricity :type eccentricity: int, float :param mean_anomaly: Mean anomaly, as an Angle object :type mean_anomaly: :py:class:`Angle` :returns: A tuple with two Angle objects: Eccentric and true anomalies :rtype: tuple :raises: TypeError if input values are of wrong type. >>> eccentricity = 0.1 >>> mean_anomaly = Angle(5.0) >>> e, v = kepler_equation(eccentricity, mean_anomaly) >>> print(round(e(), 6)) 5.554589 >>> print(round(v(), 6)) 6.139762 >>> eccentricity = 0.99 >>> mean_anomaly = Angle(2.0) >>> e, v = kepler_equation(eccentricity, mean_anomaly) >>> print(round(e(), 6)) 32.361007 >>> print(round(v(), 6)) 152.542134 >>> eccentricity = 0.99 >>> mean_anomaly = Angle(5.0) >>> e, v = kepler_equation(eccentricity, mean_anomaly) >>> print(round(e(), 6)) 45.361023 >>> print(round(v(), 6)) 160.745616 >>> eccentricity = 0.99 >>> mean_anomaly = Angle(1.0) >>> e, v = kepler_equation(eccentricity, mean_anomaly) >>> print(round(e(), 6)) 24.725822 >>> print(round(v(), 6)) 144.155952 >>> e, v = kepler_equation(0.999, Angle(7.0)) >>> print(round(e(), 7)) 52.2702615 >>> print(round(v(), 6)) 174.780018 >>> e, v = kepler_equation(0.99, Angle(0.2, radians=True)) >>> print(round(e(), 8)) 61.13444578 >>> print(round(v(), 6)) 166.311977 """ # First check that input values are of correct types if not ( isinstance(eccentricity, (int, float)) and isinstance(mean_anomaly, Angle) ): raise TypeError("Invalid input types") # Let's implement the third method (from Roger Sinnot), page 206 # First, compute the eccentric anomaly m = mean_anomaly.rad() ecc = eccentricity f = copysign(1.0, m) m = abs(m) / (2.0 * pi) m = (m - iint(m)) * 2.0 * pi * f if m < 0.0: m += 2.0 * pi f = 1.0 if m > pi: f = -1 m = 2.0 * pi - m e0 = pi / 2.0 d = pi / 4.0 ef = 0.0 while abs(e0 - ef) > TOL: ef = e0 m1 = e0 - ecc * sin(e0) s = copysign(1.0, m - m1) e0 += d * s d /= 2.0 e = Angle(e0 * f, radians=True) # Now, compute the true anomaly er = e.rad() v = 2.0 * atan(sqrt((1.0 + ecc) / (1.0 - ecc)) * tan(er / 2.0)) return e, Angle(v, radians=True) def orbital_elements(epoch, parameters1, parameters2): """This function computes the orbital elements for a given epoch, according to the parameters beeing passed as arguments. :param epoch: Epoch to compute orbital elements, as an Epoch object :type epoch: :py:class:`Epoch` :param parameters1: First set of parameters :type parameters1: list :param parameters2: Second set of parameters :type parameters2: list :returns: A tuple containing the following six orbital elements: - Mean longitude of the planet (Angle) - Semimajor axis of the orbit (float, astronomical units) - eccentricity of the orbit (float) - inclination on the plane of the ecliptic (Angle) - longitude of the ascending node (Angle) - argument of the perihelion (Angle) :rtype: tuple :raises: TypeError if input values are of wrong type. """ # First check that input values are of correct types if not (isinstance(epoch, Epoch) and isinstance(parameters1, list) and isinstance(parameters2, list)): raise TypeError("Invalid input types") # Define an auxiliary function def compute_element(t, param): return param[0] + t * (param[1] + t * (param[2] + t * param[3])) # Compute the time parameter t = (epoch - JDE2000) / 36525.0 # Compute the orbital elements ll = compute_element(t, parameters2[0]) a = compute_element(t, parameters1[1]) e = compute_element(t, parameters1[2]) if len(parameters2) == 4: i = compute_element(t, parameters2[1]) omega = compute_element(t, parameters2[2]) pie = compute_element(t, parameters2[3]) else: i = compute_element(t, parameters2[3]) omega = compute_element(t, parameters2[4]) pie = compute_element(t, parameters2[5]) arg = pie - omega ll = Angle(ll) i = Angle(i) omega = Angle(omega) arg = Angle(arg) return ll, a, e, i, omega, arg def velocity(r, a): """This function computes the instantaneous velocity of the moving body, in kilometers per second, for an unperturbed elliptic orbit. :param r: Distance of the body to the Sun, in Astronomical Units :type r: float :param a: Semimajor axis of the orbit, in Astronomical Units :type a: float :returns: Velocity of the body, in kilometers per second :rtype: float :raises: TypeError if input values are of wrong type. >>> r = 1.0 >>> a = 17.9400782 >>> v = velocity(r, a) >>> print(round(v, 2)) 41.53 """ if not (isinstance(r, float) and isinstance(a, float)): raise TypeError("Invalid input types") return 42.1218 * sqrt((1.0 / r) - (1.0 / (2.0 * a))) def velocity_perihelion(e, a): """This function computes the velocity of the moving body at perihelion, in kilometers per second, for an unperturbed elliptic orbit. :param e: Orbital eccentricity :type e: float :param a: Semimajor axis of the orbit, in Astronomical Units :type a: float :returns: Velocity of the body at perihelion, in kilometers per second :rtype: float :raises: TypeError if input values are of wrong type. >>> a = 17.9400782 >>> e = 0.96727426 >>> vp = velocity_perihelion(e, a) >>> print(round(vp, 2)) 54.52 """ if not (isinstance(e, float) and isinstance(a, float)): raise TypeError("Invalid input types") temp = sqrt((1.0 + e) / (1.0 - e)) return 29.7847 * temp / sqrt(a) def velocity_aphelion(e, a): """This function computes the velocity of the moving body at aphelion, in kilometers per second, for an unperturbed elliptic orbit. :param e: Orbital eccentricity :type e: float :param a: Semimajor axis of the orbit, in Astronomical Units :type a: float :returns: Velocity of the body at aphelion, in kilometers per second :rtype: float :raises: TypeError if input values are of wrong type. >>> a = 17.9400782 >>> e = 0.96727426 >>> va = velocity_aphelion(e, a) >>> print(round(va, 2)) 0.91 """ if not (isinstance(e, float) and isinstance(a, float)): raise TypeError("Invalid input types") temp = sqrt((1.0 - e) / (1.0 + e)) return 29.7847 * temp / sqrt(a) def length_orbit(e, a): """This function computes the length of an elliptic orbit given its eccentricity and semimajor axis. :param e: Orbital eccentricity :type e: float :param a: Semimajor axis of the orbit, in Astronomical Units :type a: float :returns: Length of the orbit in Astronomical Units :rtype: float :raises: TypeError if input values are of wrong type. >>> a = 17.9400782 >>> e = 0.96727426 >>> length = length_orbit(e, a) >>> print(round(length, 2)) 77.06 """ if not (isinstance(e, float) and isinstance(a, float)): raise TypeError("Invalid input types") # Let's start computing the semi-minor axis b = a * sqrt(1.0 - e * e) # Use one formula or another depending on eccentricity if e < 0.95: aa = (a + b) / 2.0 gg = sqrt(a * b) hh = (2.0 * a * b) / (a + b) length = pi * (21.0 * aa - 2.0 * gg - 3.0 * hh) / 8.0 else: length = pi * (3.0 * (a + b) - sqrt((a + 3.0 * b) * (3.0 * a + b))) return length def passage_nodes_elliptic(omega, e, a, t, ascending=True): """This function computes the time of passage by the nodes (ascending or descending) of a given celestial object with an elliptic orbit. :param omega: Argument of the perihelion :type omega: :py:class:`Angle` :param e: Orbital eccentricity :type e: float :param a: Semimajor axis of the orbit, in Astronomical Units :type a: float :param t: Time of perihelion passage :type t: :py:class:`Epoch` :param ascending: Whether the time of passage by the ascending (True) or descending (False) node will be computed :type ascending: bool :returns: Tuple containing: - Time of passage through the node (:py:class:`Epoch`) - Radius vector when passing through the node (in AU, float) :rtype: tuple :raises: TypeError if input values are of wrong type. >>> omega = Angle(111.84644) >>> e = 0.96727426 >>> a = 17.9400782 >>> t = Epoch(1986, 2, 9.45891) >>> time, r = passage_nodes_elliptic(omega, e, a, t) >>> year, month, day = time.get_date() >>> print(year) 1985 >>> print(month) 11 >>> print(round(day, 2)) 9.16 >>> print(round(r, 4)) 1.8045 >>> time, r = passage_nodes_elliptic(omega, e, a, t, ascending=False) >>> year, month, day = time.get_date() >>> print(year) 1986 >>> print(month) 3 >>> print(round(day, 2)) 10.37 >>> print(round(r, 4)) 0.8493 """ if not (isinstance(omega, Angle) and isinstance(e, float) and isinstance(a, float) and isinstance(t, Epoch)): raise TypeError("Invalid input types") # First, get the true anomaly if ascending: v = 360.0 - omega else: v = 180.0 - omega # Compute the eccentric anomaly ee = 2.0 * atan(sqrt((1.0 - e)/(1.0 + e)) * tan(v.rad() / 2.0)) # Now compute the mean anomaly m = ee - e * sin(ee) # We need the mean motion, in degrees/day n = 0.9856076686/(a * sqrt(a)) # The time of passage will be tt = t + degrees(m) / n # And the corresponding radius vector is r = a * (1.0 - e * cos(ee)) return tt, r def passage_nodes_parabolic(omega, q, t, ascending=True): """This function computes the time of passage by the nodes (ascending or descending) of a given celestial object with a parabolic orbit. :param omega: Argument of the perihelion :type omega: :py:class:`Angle` :param q: Perihelion distance, in Astronomical Units :type q: float :param t: Time of perihelion passage :type t: :py:class:`Epoch` :param ascending: Whether the time of passage by the ascending (True) or descending (False) node will be computed :type ascending: bool :returns: Tuple containing: - Time of passage through the node (:py:class:`Epoch`) - Radius vector when passing through the node (in AU, float) :rtype: tuple :raises: TypeError if input values are of wrong type. >>> omega = Angle(154.9103) >>> q = 1.324502 >>> t = Epoch(1989, 8, 20.291) >>> time, r = passage_nodes_parabolic(omega, q, t) >>> year, month, day = time.get_date() >>> print(year) 1977 >>> print(month) 9 >>> print(round(day, 2)) 17.64 >>> print(round(r, 4)) 28.0749 >>> time, r = passage_nodes_parabolic(omega, q, t, ascending=False) >>> year, month, day = time.get_date() >>> print(year) 1989 >>> print(month) 9 >>> print(round(day, 3)) 17.636 >>> print(round(r, 4)) 1.3901 """ if not (isinstance(omega, Angle) and isinstance(q, float) and isinstance(t, Epoch)): raise TypeError("Invalid input types") # First, get the true anomaly if ascending: v = 360.0 - omega else: v = 180.0 - omega # Compute an auxiliary value s = tan(v.rad() / 2.0) s2 = s * s # Compute time of passage tt = t + 27.403895 * s * (s2 + 3.0) * q * sqrt(q) # Compute radius vector r = q * (1.0 + s2) return tt, r def phase_angle(sun_dist, earth_dist, sun_earth_dist): """This function computes the phase angle, i.e., the angle Sun-planet-Earth from the corresponding distances. :param sun_dist: Planet's distance to the Sun, in Astronomical Units :type sun_dist: float :param earth_dist: Distance from planet to Earth, in Astronomical Units :type earth_dist: float :param sun_earth_dist: Distance Sun-Earth, in Astronomical Units :type sun_earth_dist: float :returns: The phase angle, as an Angle object :rtype: :py:class:`Angle` :raises: TypeError if input values are of wrong type. >>> sun_dist = 0.724604 >>> earth_dist = 0.910947 >>> sun_earth_dist = 0.983824 >>> angle = phase_angle(sun_dist, earth_dist, sun_earth_dist) >>> print(round(angle, 2)) 72.96 """ if not (isinstance(sun_dist, float) and isinstance(earth_dist, float) and isinstance(sun_earth_dist, float)): raise TypeError("Invalid input types") angle = acos((sun_dist * sun_dist + earth_dist * earth_dist - sun_earth_dist * sun_earth_dist) / (2.0 * sun_dist * earth_dist)) angle = Angle(angle, radians=True) return angle def illuminated_fraction(sun_dist, earth_dist, sun_earth_dist): """This function computes the illuminated fraction of the disk of a planet, as seen from the Earth. :param sun_dist: Planet's distance to the Sun, in Astronomical Units :type sun_dist: float :param earth_dist: Distance from planet to Earth, in Astronomical Units :type earth_dist: float :param sun_earth_dist: Distance Sun-Earth, in Astronomical Units :type sun_earth_dist: float :returns: The illuminated fraction of the disc of a planet :rtype: float :raises: TypeError if input values are of wrong type. >>> sun_dist = 0.724604 >>> earth_dist = 0.910947 >>> sun_earth_dist = 0.983824 >>> k = illuminated_fraction(sun_dist, earth_dist, sun_earth_dist) >>> print(round(k, 3)) 0.647 """ if not (isinstance(sun_dist, float) and isinstance(earth_dist, float) and isinstance(sun_earth_dist, float)): raise TypeError("Invalid input types") k = ((sun_dist + earth_dist) * (sun_dist + earth_dist) - sun_earth_dist * sun_earth_dist) / (4.0 * sun_dist * earth_dist) return k def main(): # Let's define a small helper function def print_me(msg, val): print("{}: {}".format(msg, val)) # Let's show some uses of Coordinate functions print("\n" + 35 * "*") print("*** Use of Coordinate functions") print(35 * "*" + "\n") # Here follows a series of important parameters related to the angle # between Earth's rotation axis and the ecliptic e0 = mean_obliquity(1987, 4, 10) print( "The mean angle between Earth rotation axis and ecliptic axis for " + "1987/4/10 is:" ) print_me("Mean obliquity", e0.dms_str(n_dec=3)) # 23d 26' 27.407'' epsilon = true_obliquity(1987, 4, 10) print("'True' (instantaneous) angle between those axes for 1987/4/10 is:") print_me("True obliquity", epsilon.dms_str(n_dec=3)) # 23d 26' 36.849'' epsilon = true_obliquity(2018, 7, 29) print("'True' (instantaneous) angle between those axes for 2018/7/29 is:") print_me("True obliquity", epsilon.dms_str(True, 4)) # 23d 26' 7.2157'' # The nutation effect is separated in two components: One parallel to the # ecliptic (nutation in longitude) and other perpendicular to the ecliptic # (nutation in obliquity) print("Nutation correction in longitude for 1987/4/10:") dpsi = nutation_longitude(1987, 4, 10) print_me("Nutation in longitude", dpsi.dms_str(n_dec=3)) # 0d 0' -3.788'' print("Nutation correction in obliquity for 1987/4/10:") depsilon = nutation_obliquity(1987, 4, 10) # 0d 0' 9.443'' print_me("Nutation in obliquity", depsilon.dms_str(n_dec=3)) print("") # We can compute the effects of precession on the equatorial coordinates of # a given star, taking also into account its proper motion start_epoch = JDE2000 final_epoch = Epoch(2028, 11, 13.19) alpha0 = Angle(2, 44, 11.986, ra=True) delta0 = Angle(49, 13, 42.48) # 2h 44' 11.986'' print_me("Initial right ascension", alpha0.ra_str(n_dec=3)) print_me("Initial declination", delta0.dms_str(n_dec=2)) # 49d 13' 42.48'' pm_ra = Angle(0, 0, 0.03425, ra=True) pm_dec = Angle(0, 0, -0.0895) alpha, delta = precession_equatorial( start_epoch, final_epoch, alpha0, delta0, pm_ra, pm_dec ) print_me("Final right ascension", alpha.ra_str(n_dec=3)) # 2h 46' 11.331'' print_me("Final declination", delta.dms_str(n_dec=2)) # 49d 20' 54.54'' print("") # Something similar can also be done with the ecliptical coordinates start_epoch = JDE2000 final_epoch = Epoch(-214, 6, 30.0) lon0 = Angle(149.48194) lat0 = Angle(1.76549) print_me("Initial ecliptical longitude", round(lon0(), 5)) # 149.48194 print_me("Initial ecliptical latitude", round(lat0(), 5)) # 1.76549 lon, lat = precession_ecliptical(start_epoch, final_epoch, lon0, lat0) print_me("Final ecliptical longitude", round(lon(), 3)) # 118.704 print_me("Final ecliptical latitude", round(lat(), 3)) # 1.615 print("") # It is possible to compute with relative accuracy the proper motion of the # stars, taking into account their distance to Sun and relative velocity ra = Angle(6, 45, 8.871, ra=True) dec = Angle(-16.716108) pm_ra = Angle(0, 0, -0.03847, ra=True) pm_dec = Angle(0, 0, -1.2053) dist = 2.64 vel = -7.6 alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec, -1000.0) print_me("Right ascension, year 2000", ra.ra_str(True, 2)) print_me("Right ascension, year 1000", alpha.ra_str(True, 2)) # 6h 45' 47.16'' print_me("Declination, year 2000", dec.dms_str(True, 1)) print_me("Declination, year 1000", delta.dms_str(True, 1)) # -16d 22' 56.0'' print("") # This module provides a series of functions to convert between equatorial, # ecliptical, horizontal and galactic coordinates ra = Angle(7, 45, 18.946, ra=True) dec = Angle(28, 1, 34.26) epsilon = Angle(23.4392911) lon, lat = equatorial2ecliptical(ra, dec, epsilon) print_me("Equatorial to ecliptical. Longitude", round(lon(), 5)) # 113.21563 print_me("Equatorial to ecliptical. Latitude", round(lat(), 5)) # 6.68417 print("") lon = Angle(113.21563) lat = Angle(6.68417) epsilon = Angle(23.4392911) ra, dec = ecliptical2equatorial(lon, lat, epsilon) print_me("Ecliptical to equatorial. Right ascension", ra.ra_str(n_dec=3)) # 7h 45' 18.946'' print_me("Ecliptical to equatorial. Declination", dec.dms_str(n_dec=2)) # 28d 1' 34.26'' print("") lon = Angle(77, 3, 56) lat = Angle(38, 55, 17) ra = Angle(23, 9, 16.641, ra=True) dec = Angle(-6, 43, 11.61) theta0 = Angle(8, 34, 57.0896, ra=True) eps = Angle(23, 26, 36.87) # Compute correction to convert from mean to apparent sidereal time delta = Angle(0, 0, ((-3.868 * cos(eps.rad())) / 15.0), ra=True) theta0 += delta h = theta0 - lon - ra azi, ele = equatorial2horizontal(h, dec, lat) print_me("Equatorial to horizontal: Azimuth", round(azi, 3)) # 68.034 print_me("Equatorial to horizontal: Elevation", round(ele, 3)) # 15.125 print("") azi = Angle(68.0337) ele = Angle(15.1249) lat = Angle(38, 55, 17) h, dec = horizontal2equatorial(azi, ele, lat) print_me("Horizontal to equatorial. Hour angle", round(h, 4)) # 64.3521 print_me("Horizontal to equatorial. Declination", dec.dms_str(n_dec=0)) # -6d 43' 12.0'' print("") ra = Angle(17, 48, 59.74, ra=True) dec = Angle(-14, 43, 8.2) lon, lat = equatorial2galactic(ra, dec) print_me("Equatorial to galactic. Longitude", round(lon, 4)) # 12.9593 print_me("Equatorial to galactic. Latitude", round(lat, 4)) # 6.0463 print("") lon = Angle(12.9593) lat = Angle(6.0463) ra, dec = galactic2equatorial(lon, lat) print_me("Galactic to equatorial. Right ascension", ra.ra_str(n_dec=1)) # 17h 48' 59.7'' print_me("Galactic to equatorial. Declination", dec.dms_str(n_dec=0)) # -14d 43' 8.0'' print("") # Get the ecliptic longitudes of the two points of the ecliptic which are # on the horizon, as well as the angle between the ecliptic and the horizon sidereal_time = Angle(5.0, ra=True) lat = Angle(51.0) epsilon = Angle(23.44) lon1, lon2, i = ecliptic_horizon(sidereal_time, lat, epsilon) print_me( "Longitude of ecliptic point #1 on the horizon", lon1.dms_str(n_dec=1) ) # 169d 21' 29.9'' print_me( "Longitude of ecliptic point #2 on the horizon", lon2.dms_str(n_dec=1) ) # 349d 21' 29.9'' print_me("Angle between the ecliptic and the horizon", round(i, 0)) # 62.0 print("") # Let's compute the angle of the diurnal path of a celestial body relative # to the horizon at the time of rising and setting dec = Angle(23.44) lat = Angle(40.0) j = diurnal_path_horizon(dec, lat) print_me( "Diurnal path vs. horizon angle at time of rising and setting", j.dms_str(n_dec=1), ) # 45d 31' 28.4'' print("") # There is a function to compute the times (in hours of the day) of rising, # transit and setting of a given celestial body longitude = Angle(71, 5, 0.0) latitude = Angle(42, 20, 0.0) alpha1 = Angle(2, 42, 43.25, ra=True) delta1 = Angle(18, 2, 51.4) alpha2 = Angle(2, 46, 55.51, ra=True) delta2 = Angle(18, 26, 27.3) alpha3 = Angle(2, 51, 7.69, ra=True) delta3 = Angle(18, 49, 38.7) h0 = Angle(-0.5667) delta_t = 56.0 theta0 = Angle(11, 50, 58.1, ra=True) rising, transit, setting = times_rise_transit_set( longitude, latitude, alpha1, delta1, alpha2, delta2, alpha3, delta3, h0, delta_t, theta0, ) print_me("Time of rising (hours of day)", round(rising, 4)) # 12.4238 print_me("Time of transit (hours of day)", round(transit, 3)) # 19.675 print_me("Time of setting (hours of day, next day)", round(setting, 3)) # 2.911 print("") # The air in the atmosphere introduces an error in the elevation due to the # refraction. We can compute the true (airless) elevation from the apparent # elevation, and viceversa apparent_elevation = Angle(0, 30, 0.0) true_elevation = refraction_apparent2true(apparent_elevation) print_me( "True elevation for an apparent elevation of 30'", true_elevation.dms_str(n_dec=1), ) # 1' 14.7'' true_elevation = Angle(0, 33, 14.76) apparent_elevation = refraction_true2apparent(true_elevation) print_me( "Apparent elevation for a true elevation of 33' 14.76''", apparent_elevation.dms_str(n_dec=2), ) # 57' 51.96'' print("") # The angular separation between two celestial objects can be easily # computed with the 'angular_separation()' function alpha1 = Angle(14, 15, 39.7, ra=True) delta1 = Angle(19, 10, 57.0) alpha2 = Angle(13, 25, 11.6, ra=True) delta2 = Angle(-11, 9, 41.0) sep_ang = angular_separation(alpha1, delta1, alpha2, delta2) print_me( "Angular separation between two given celestial bodies (degrees)", round(sep_ang, 3), ) # 32.793 print("") # We can compute the minimum angular separation achieved between two # celestial objects. For that, we must provide the positions at three # equidistant epochs: # EPOCH: Sep 13th, 1978, 0h TT: alpha1_1 = Angle(10, 29, 44.27, ra=True) delta1_1 = Angle(11, 2, 5.9) alpha2_1 = Angle(10, 33, 29.64, ra=True) delta2_1 = Angle(10, 40, 13.2) # EPOCH: Sep 14th, 1978, 0h TT: alpha1_2 = Angle(10, 36, 19.63, ra=True) delta1_2 = Angle(10, 29, 51.7) alpha2_2 = Angle(10, 33, 57.97, ra=True) delta2_2 = Angle(10, 37, 33.4) # EPOCH: Sep 15th, 1978, 0h TT: alpha1_3 = Angle(10, 43, 1.75, ra=True) delta1_3 = Angle(9, 55, 16.7) alpha2_3 = Angle(10, 34, 26.22, ra=True) delta2_3 = Angle(10, 34, 53.9) a = minimum_angular_separation( alpha1_1, delta1_1, alpha1_2, delta1_2, alpha1_3, delta1_3, alpha2_1, delta2_1, alpha2_2, delta2_2, alpha2_3, delta2_3, ) # Epoch fraction: print_me("Minimum angular separation, epoch fraction", round(a[0], 6)) # -0.370726 # NOTE: Given that 'n' is negative, and Sep 14th is the middle epoch (n=0), # then the minimum angular separation is achieved on Sep 13th, specifically # at: 1.0 - 0.370726 = 0.629274 => Sep 13.629274 = Sep 13th, 15h 6' 9'' # Minimum angular separation: print_me("Minimum angular separation", a[1].dms_str(n_dec=0)) # 3' 44.0'' print("") # If two objects have the same right ascension, then the relative position # angle between them must be 0 (or 180) alpha1 = Angle(14, 15, 39.7, ra=True) delta1 = Angle(19, 10, 57.0) alpha2 = Angle(14, 15, 39.7, ra=True) # Same as alpha1 delta2 = Angle(-11, 9, 41.0) pos_ang = relative_position_angle(alpha1, delta1, alpha2, delta2) print_me("Relative position angle", round(pos_ang, 1)) # 0.0 print("") # Planetary conjunctions may be computed with the appropriate function alpha1_1 = Angle(10, 24, 30.125, ra=True) delta1_1 = Angle(6, 26, 32.05) alpha1_2 = Angle(10, 25, 0.342, ra=True) delta1_2 = Angle(6, 10, 57.72) alpha1_3 = Angle(10, 25, 12.515, ra=True) delta1_3 = Angle(5, 57, 33.08) alpha1_4 = Angle(10, 25, 6.235, ra=True) delta1_4 = Angle(5, 46, 27.07) alpha1_5 = Angle(10, 24, 41.185, ra=True) delta1_5 = Angle(5, 37, 48.45) alpha2_1 = Angle(10, 27, 27.175, ra=True) delta2_1 = Angle(4, 4, 41.83) alpha2_2 = Angle(10, 26, 32.410, ra=True) delta2_2 = Angle(3, 55, 54.66) alpha2_3 = Angle(10, 25, 29.042, ra=True) delta2_3 = Angle(3, 48, 3.51) alpha2_4 = Angle(10, 24, 17.191, ra=True) delta2_4 = Angle(3, 41, 10.25) alpha2_5 = Angle(10, 22, 57.024, ra=True) delta2_5 = Angle(3, 35, 16.61) alpha1_list = [alpha1_1, alpha1_2, alpha1_3, alpha1_4, alpha1_5] delta1_list = [delta1_1, delta1_2, delta1_3, delta1_4, delta1_5] alpha2_list = [alpha2_1, alpha2_2, alpha2_3, alpha2_4, alpha2_5] delta2_list = [delta2_1, delta2_2, delta2_3, delta2_4, delta2_5] pc = planetary_conjunction(alpha1_list, delta1_list, alpha2_list, delta2_list) print_me("Epoch fraction 'n' for planetary conjunction", round(pc[0], 5)) # 0.23797 print_me( "Difference in declination at conjunction", pc[1].dms_str(n_dec=1) ) # 2d 8' 21.8'' print("") # A planetary conjunction with a star is a little bit simpler alpha_1 = Angle(15, 3, 51.937, ra=True) delta_1 = Angle(-8, 57, 34.51) alpha_2 = Angle(15, 9, 57.327, ra=True) delta_2 = Angle(-9, 9, 3.88) alpha_3 = Angle(15, 15, 37.898, ra=True) delta_3 = Angle(-9, 17, 37.94) alpha_4 = Angle(15, 20, 50.632, ra=True) delta_4 = Angle(-9, 23, 16.25) alpha_5 = Angle(15, 25, 32.695, ra=True) delta_5 = Angle(-9, 26, 1.01) alpha_star = Angle(15, 17, 0.446, ra=True) delta_star = Angle(-9, 22, 58.47) alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5] delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5] pc = planet_star_conjunction(alpha_list, delta_list, alpha_star, delta_star) print_me("Epoch fraction 'n' for planetary conjunction with star", round(pc[0], 4)) # 0.2551 print_me("Difference in declination with star at conjunction", pc[1].dms_str(n_dec=0)) # 3' 38.0'' print("") # It is possible to compute when a planet and two other stars will be in a # straight line alpha_1 = Angle(7, 55, 55.36, ra=True) delta_1 = Angle(21, 41, 3.0) alpha_2 = Angle(7, 58, 22.55, ra=True) delta_2 = Angle(21, 35, 23.4) alpha_3 = Angle(8, 0, 48.99, ra=True) delta_3 = Angle(21, 29, 38.2) alpha_4 = Angle(8, 3, 14.66, ra=True) delta_4 = Angle(21, 23, 47.5) alpha_5 = Angle(8, 5, 39.54, ra=True) delta_5 = Angle(21, 17, 51.4) alpha_star1 = Angle(7, 34, 16.40, ra=True) delta_star1 = Angle(31, 53, 51.2) alpha_star2 = Angle(7, 45, 0.10, ra=True) delta_star2 = Angle(28, 2, 12.5) alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5] delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5] n = planet_stars_in_line(alpha_list, delta_list, alpha_star1, delta_star1, alpha_star2, delta_star2) print_me("Epoch fraction 'n' when bodies are in a straight line", round(n, 4)) # 0.2233 print("") # The function 'straight_line()' computes if three celestial bodies are in # line providing the angle with which the bodies differ from a great circle alpha1 = Angle(5, 32, 0.40, ra=True) delta1 = Angle(0, -17, 56.9) alpha2 = Angle(5, 36, 12.81, ra=True) delta2 = Angle(-1, 12, 7.0) alpha3 = Angle(5, 40, 45.52, ra=True) delta3 = Angle(-1, 56, 33.3) psi, omega = straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3) print_me("Angle deviation from a straight line", psi.dms_str(n_dec=0)) # 7d 31' 1.0'' print_me("Angular distance of central point to the straight line", omega.dms_str(n_dec=0)) # -5' 24.0'' print("") # Let's compute the size of the smallest circle that contains three bodies alpha1 = Angle(12, 41, 8.63, ra=True) delta1 = Angle(-5, 37, 54.2) alpha2 = Angle(12, 52, 5.21, ra=True) delta2 = Angle(-4, 22, 26.2) alpha3 = Angle(12, 39, 28.11, ra=True) delta3 = Angle(-1, 50, 3.7) d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3) print_me( "Diameter of smallest circle containing three celestial bodies", d.dms_str(n_dec=0), ) # 4d 15' 49.0'' print("") # Now, let's find the apparent position of a star (Theta Persei) for a # given epoch epoch = Epoch(2028, 11, 13.19) alpha = Angle(2, 46, 11.331, ra=True) delta = Angle(49, 20, 54.54) sun_lon = Angle(231.328) app_alpha, app_delta = apparent_position(epoch, alpha, delta, sun_lon) print_me("Apparent right ascension", app_alpha.ra_str(n_dec=2)) # 2h 46' 14.39'' print_me("Apparent declination", app_delta.dms_str(n_dec=2)) # 49d 21' 7.45'' print("") # Convert orbital elements of an object from one equinox to another epoch0 = Epoch(2358042.5305) epoch = Epoch(2433282.4235) i0 = Angle(47.122) arg0 = Angle(151.4486) lon0 = Angle(45.7481) i1, arg1, lon1 = orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0) print_me("New inclination", round(i1(), 3)) # 47.138 print_me("New argument of perihelion", round(arg1(), 4)) # 151.4782 print_me("New longitude of ascending node", round(lon1(), 4)) # 48.6037 print("") # Compute the eccentric and true anomalies using Kepler's equation eccentricity = 0.1 mean_anomaly = Angle(5.0) e, v = kepler_equation(eccentricity, mean_anomaly) print_me("Eccentric anomaly, Case #1", round(e(), 6)) # 5.554589 print_me("True anomaly, Case #1", round(v(), 6)) # 6.139762 e, v = kepler_equation(0.99, Angle(0.2, radians=True)) print_me("Eccentric anomaly, Case #2", round(e(), 8)) # 61.13444578 print_me("True anomaly, Case #2", round(v(), 6)) # 166.311977 print("") # Compute the velocity of a body in a given point of its (unperturbated # elliptic) orbit r = 1.0 a = 17.9400782 v = velocity(r, a) print_me("Velocity at 1 AU", round(v, 2)) # 41.53 # Compute the velocity at perihelion e = 0.96727426 vp = velocity_perihelion(e, a) print_me("Velocity at perihelion", round(vp, 2)) # 54.52 # Compute the velocity at aphelion va = velocity_aphelion(e, a) print_me("Velocity at aphelion", round(va, 2)) # 0.91 # Calculate the length of the orbit length = length_orbit(e, a) print_me("Length of the orbit (AU)", round(length, 2)) # 77.06 print("") # Passage through the nodes of an elliptic orbit omega = Angle(111.84644) e = 0.96727426 a = 17.9400782 t = Epoch(1986, 2, 9.45891) time, r = passage_nodes_elliptic(omega, e, a, t) y, m, d = time.get_date() d = round(d, 2) print("Time of passage through ascending node: {}/{}/{}".format(y, m, d)) # 1985/11/9.16 print("Radius vector at ascending node: {}".format(round(r, 4))) # 1.8045 # Passage through the nodes of a parabolic orbit omega = Angle(154.9103) q = 1.324502 t = Epoch(1989, 8, 20.291) time, r = passage_nodes_parabolic(omega, q, t, ascending=False) y, m, d = time.get_date() d = round(d, 2) print("Time of passage through descending node: {}/{}/{}".format(y, m, d)) # 1989/9/17.64 print("Radius vector at descending node: {}".format(round(r, 4))) # 1.3901 print("") # Compute the phase angle sun_dist = 0.724604 earth_dist = 0.910947 sun_earth_dist = 0.983824 angle = phase_angle(sun_dist, earth_dist, sun_earth_dist) print_me("Phase angle", round(angle, 2)) # 72.96 # Now, let's compute the illuminated fraction of the disk k = illuminated_fraction(sun_dist, earth_dist, sun_earth_dist) print_me("Illuminated fraction of planet disk", round(k, 3)) # 0.647 if __name__ == "__main__": main()