Source code for giant.catalogs.utilities




r"""
This submodule provides utility constants and functions for working with star data in GIANT.

Most of these utilities are focused on conversions of either units or of representations (ie a bearing to a unit
vector) with the exception of applying proper motion and computing the distance between 2 bearings.  For more details on
what is contained refer to the following summary tables and the documentation for each constant/function.
"""

import datetime

from typing import Union

import numpy as np

import pandas as pd

from giant.utilities.spherical_coordinates import unit_to_radec, radec_to_unit


__all__ = ['DEG2RAD', 'RAD2DEG', 'DEG2MAS', 'MAS2DEG', 'RAD2MAS', 'MAS2RAD', 'PARSEC2KM', 'AVG_STAR_DIST',
           'SI_DAYS_PER_YEAR', 'SI_SECONDS_PER_DAY', 'MJD_EPOCH', 
           'timedelta_to_si_years', 'datetime_to_mjd_years', 'apply_proper_motion']
"""
Things to import if someone wants to do from giant.catalogs.utilities import *
"""

# CONSTANTS.

DEG2RAD: float = np.pi / 180  # rad/deg
r"""
This constant converts from units of degrees to units of radians through multiplication.

That is ``angle_rad = angle_deg*DEG2RAD`` where ``angle_rad`` is the angle in radians and ``angle_deg`` is the angle in 
degrees.

Mathematically this is :math:`\frac{\pi}{180}`.
"""

RAD2DEG: float = 180 / np.pi  # deg/rad
r"""
This constant converts from units of radians to units of degrees through multiplication.

That is ``angle_deg = angle_rad*RAD2DEG`` where ``angle_rad`` is the angle in radians and ``angle_deg`` is the angle in 
degrees.

Mathematically this is :math:`\frac{180}{\pi}`.
"""

DEG2MAS: float = 3600 * 1000  # mas/deg
r"""
This constant converts from units of degrees to units of milli-arc-seconds through multiplication.

That is ``angle_mas = angle_deg*DEG2MAS`` where ``angle_mas`` is the angle in milli-arc-seconds and ``angle_deg`` is the 
angle in degrees.

Mathematically this is :math:`3600000`.
"""

MAS2DEG: float = 1 / DEG2MAS  # deg/mas
r"""
This constant converts from units of milli-arc-seconds to units of degrees through multiplication.

That is ``angle_deg = angle_mas*MAS2DEG`` where ``angle_mas`` is the angle in milli-arc-seconds and ``angle_deg`` is the 
angle in degrees.

Mathematically this is :math:`\frac{1}{3600000}`.
"""

RAD2MAS: float = RAD2DEG * DEG2MAS
r"""
This constant converts from units of radians to units of milli-arc-seconds through multiplication.

That is ``angle_mas = angle_rad*RAD2MAS`` where ``angle_mas`` is the angle in milli-arc-seconds and ``angle_rad`` is the 
angle in radians.

Mathematically this is :math:`\frac{180}{3600000\pi}`.
"""

MAS2RAD: float = 1 / RAD2MAS
r"""
This constant converts from units of milli-arc-seconds to units of radians through multiplication.

That is ``angle_rad = angle_mas*MAS2RAD`` where ``angle_mas`` is the angle in milli-arc-seconds and ``angle_rad`` is the 
angle in radians.

Mathematically this is :math:`\frac{3600000\pi}{180}`.
"""

PARSEC2KM: float = 30856775814913.673  # km
r"""
This constant converts from units of parsecs to units of kilometers through multiplication.

That is ``distance_km = distance_parsec*PARSEC2KM`` where ``distance_km`` is the distance in kilometers and 
``distance_parsec`` is the distance in parsecs.

Mathematically this is :math:`\frac{3600000\pi}{180}`.
"""

AVG_STAR_DIST: float = 5.428047027e15  # km
"""
The average distance of stars from the UCAC4 catalog in kilometers.  

This value is used to set the distance for stars for which there is no distance information available.
"""

AVG_STAR_DIST_SIGMA = 20 / (PARSEC2KM*1000/AVG_STAR_DIST) ** 2 * 1000 * PARSEC2KM
"""
The sigma value to use for stars with parallax that is not well known or known at all.

This is meant to be very high so that the stars are mostly ignored if using weighted estimation.
"""

SI_DAYS_PER_YEAR: float = 365.25  # days
"""
This constant provides the number of SI days in a SI year.
"""

SI_SECONDS_PER_DAY: int = 86400  # seconds
"""
This constant provides the number of SI seconds in a SI day.
"""

MJD_EPOCH: datetime.datetime = datetime.datetime(1858, 11, 17)  # November 17, 1858
"""
This constant provides the standard modified Julian date epoch, November 17, 1858, as a datetime
"""


# UTILITY FUNCTIONS
[docs] def timedelta_to_si_years(delta: datetime.timedelta) -> float: """ This function converts a python timedelta object to a fractional number of SI years. The timedelta object is first converted to seconds using method ``total_seconds``, and this is then converted to SI years. :param delta: The python timedelta object to be converted to fractional SI years :return: The length of time covered by the time delta in units of fractional SI years """ return delta.total_seconds() / SI_SECONDS_PER_DAY / SI_DAYS_PER_YEAR
[docs] def datetime_to_mjd_years(date: datetime.datetime) -> float: """ This function converts a python datetime objects to the number of SI years since the MJD Epoch of November 17, 1858. This is computed by computing the time delta between the :attr:`.MJD_EPOCH` and the input datetime object, and then using :func:`.timedelta_to_si_years` to convert to the fractional years since the epoch. :param date: the python datetime object to be converted to MJD years :return: the number of SI years since November 17, 1858 """ return timedelta_to_si_years(date - MJD_EPOCH)
[docs] def apply_proper_motion(star_records: pd.DataFrame, new_time: Union[float, datetime.datetime], copy: bool = True) -> pd.DataFrame: """ This function adjusts the right ascension and declination of stars to a new time. The right ascension and declination are updated using the corresponding proper motion of the stars. The formulation used here assumes constant linear velocity as described in section 1.2.8 of "The Hipparcos and Tycho2 Catalogs". The bearing measurement is converted to a unit vector, which is then updated using vector addition with the delta applied along the vectors of increasing right ascension and increasing declination. This model also allows for consideration of a radial velocity, but that is currently not implemented. The stars input into this method should be a pandas dataframe with the GIANT format. Specifically, this function requires the dataframe to have columns of ``['ra', 'dec', 'ra_proper_motion', 'dec_proper_motion', 'epoch']`` with units of degrees, degrees/year, and SI years (since January 1, 1) respectively. The updated bearing can be stored either in a copy of the dataframe, or in-place, depending on the ``copy`` key word argument. Either way the resulting dataframe is returned. The ``new_time`` parameter should either be a datetime object, or a float of the modified julian years for the desired time. The ``copy`` flag states whether to return a copy of the dataframe with the updates applied (recommended), or to make the updates in place. :param star_records: a pandas dataframe containing the bearing and proper motion of star records to be updated :param new_time: the new epoch to calculate the star positions at expressed as a mjy float or python datetime object :param copy: An option flag indicating whether to make a copy of star_records before applying proper motion :return: a pandas dataframe containing the star records with bearing values updated to the new epoch """ if copy: out = star_records.copy() else: out = star_records # convert the ra and dec values into radians ra0: np.ndarray = out['ra'].to_numpy() * DEG2RAD dec0: np.ndarray = out['dec'].to_numpy() * DEG2RAD # assume linear motion based on Hipparcos Vol 1 # compute the unit vector for each star r_unit = radec_to_unit(ra0, dec0).reshape(3, -1) # compute the unit vector in the direction of increasing right ascension p_unit = np.vstack([-np.sin(ra0), np.cos(ra0), np.zeros(ra0.shape)]) # compute the unit vector in the direction of increasing declination q_unit = np.vstack([-np.sin(dec0) * np.cos(ra0), -np.sin(dec0) * np.sin(ra0), np.cos(dec0)]) # compute the change in distance per year (future expansion) zeta0 = 0 start_time = out['epoch'].to_numpy() # compute the time delta if isinstance(new_time, datetime.datetime): new_time = timedelta_to_si_years(new_time - datetime.datetime(1, 1, 1)) timedelta = new_time - start_time # compute the new direction vector r_unit_new = r_unit * (1 + zeta0 * timedelta) + (p_unit * out['ra_proper_motion'].to_numpy() * DEG2RAD + q_unit * out['dec_proper_motion'].to_numpy() * DEG2RAD) * timedelta r_unit_new /= np.linalg.norm(r_unit_new, axis=0, keepdims=True) # compute the new ra/dec and store it ra, dec = unit_to_radec(r_unit_new) out['ra'] = ra * RAD2DEG out['dec'] = dec * RAD2DEG # compute the new uncertainties for ra and dec out['ra_sigma'] = np.sqrt(out['ra_sigma'] ** 2 + timedelta ** 2 * out['ra_pm_sigma'] ** 2) out['dec_sigma'] = np.sqrt(out['dec_sigma'] ** 2 + timedelta ** 2 * out['dec_pm_sigma'] ** 2) # update the epochs out["epoch"] = new_time return out