diff --git a/episodes/02-coords.md b/episodes/02-coords.md index 5d748579..8789a3af 100644 --- a/episodes/02-coords.md +++ b/episodes/02-coords.md @@ -395,7 +395,7 @@ perpendicular to the direction of the stream (the y-axis in Figure 1). These are called the $\phi_1$ and $\phi_2$ coordinates, respectively. ```python -from gala.coordinates import GD1Koposov10 +from gd1 import GD1Koposov10 gd1_frame = GD1Koposov10() gd1_frame @@ -405,6 +405,21 @@ gd1_frame ``` +::::::::::::::::::::::::::::::::::::::::: callout + +## GD1Koposov10 import + +The `GD1Koposov10` reference frame is part of the `gala` package. For convience we provide +the stand alone file as part of the `student_download` zip file you downloaded. If your notebook +is not in your `student_download` directory, you will need to add the following before the import +statement +```python +import sys +sys.path.append() +``` +Where `` is the path to your `student_download` directory ending with `student_download`. +:::::::::::::::::::::::::::::::::::::::::::::::::: + We can use it to find the coordinates of Betelgeuse in the GD-1 frame, like this: diff --git a/episodes/03-transform.md b/episodes/03-transform.md index 85d07584..7fb2c7b6 100644 --- a/episodes/03-transform.md +++ b/episodes/03-transform.md @@ -63,17 +63,21 @@ Whether you are working from a new notebook or coming back from a checkpoint, reloading the data will save you from having to run the query again. If you are starting this episode here or starting this episode in a new notebook, -you will need to run the following lines of code. +you will need to run the following lines of code. The last two lines of the import +statements assume your notebook is being run in the `student_download` directory. +If you are running it from a different directory you will need to add the path using +`sys.path.append()` where `` is +the path to your `student_download` directory ending with `student_download`. This imports previously imported functions: ```python import astropy.units as u from astropy.coordinates import SkyCoord -from gala.coordinates import GD1Koposov10 from astropy.table import Table from episode_functions import * +from gd1 import GD1Koposov10 ``` The following code loads in the data (instructions for downloading data can be @@ -430,10 +434,11 @@ That might seem like a strange thing to do, but here is the motivation: With this preparation, we can use `reflex_correct` from Gala ([documentation here](https://gala-astro.readthedocs.io/en/latest/api/gala.coordinates.reflex_correct.html)) -to correct for the motion of the solar system. +to correct for the motion of the solar system which we have included as a stand alone file +in the `student_download`. ```python -from gala.coordinates import reflex_correct +from reflex import reflex_correct skycoord_gd1 = reflex_correct(transformed) ``` diff --git a/episodes/05-select.md b/episodes/05-select.md index 36030384..27e630e7 100644 --- a/episodes/05-select.md +++ b/episodes/05-select.md @@ -64,7 +64,12 @@ Whether you are working from a new notebook or coming back from a checkpoint, reloading the data will save you from having to run the query again. If you are starting this episode here or starting this episode in a new notebook, -you will need to run the following lines of code. +you will need to run the following lines of code. The last two lines of the import +statements assume your notebook is being run in the `student_download` directory. +If you are running it from a different directory you will need to add the path using +`sys.path.append()` where `` is +the path to your `student_download` directory ending with `student_download`. + This imports previously imported functions: @@ -77,6 +82,8 @@ import matplotlib.pyplot as plt import pandas as pd from episode_functions import * +from gd1 import GD1Koposov10 +from reflex import reflex_correct ``` The following code loads in the data (instructions for downloading data can be diff --git a/student_download/episode_functions.py b/student_download/episode_functions.py index e8112947..fb92c791 100644 --- a/student_download/episode_functions.py +++ b/student_download/episode_functions.py @@ -7,7 +7,8 @@ import astropy.units as u from astropy.coordinates import SkyCoord -from gala.coordinates import GD1Koposov10, reflex_correct +from gd1 import GD1Koposov10 +from reflex import reflex_correct ########################## # Episode 2 diff --git a/student_download/gd1.py b/student_download/gd1.py new file mode 100644 index 00000000..b324c5a9 --- /dev/null +++ b/student_download/gd1.py @@ -0,0 +1,96 @@ +"""Astropy coordinate class for the GD-1 coordinate system""" + +import astropy.coordinates as coord +import astropy.units as u +import numpy as np +from astropy.coordinates import frame_transform_graph + +__all__ = ["GD1Koposov10"] + + +class GD1Koposov10(coord.BaseCoordinateFrame): + """ + A Heliocentric spherical coordinate system defined by the orbit of the GD1 stream, + as described in Koposov et al. 2010 (see: ``_). + + For more information about this class, see the Astropy documentation on coordinate + frames in :mod:`~astropy.coordinates`. + + Parameters + ---------- + representation : :class:`~astropy.coordinates.BaseRepresentation` or None + A representation object or None to have no data (or use the other keywords) + phi1 : angle_like, optional, must be keyword + The longitude-like angle corresponding to GD-1's orbit. + phi2 : angle_like, optional, must be keyword + The latitude-like angle corresponding to GD-1's orbit. + distance : :class:`~astropy.units.Quantity`, optional, must be keyword + The Distance for this object along the line-of-sight. + pm_phi1_cosphi2 : :class:`~astropy.units.Quantity`, optional, must be keyword + The proper motion in the longitude-like direction corresponding to + the GD-1 stream's orbit. + pm_phi2 : :class:`~astropy.units.Quantity`, optional, must be keyword + The proper motion in the latitude-like direction perpendicular to the + GD-1 stream's orbit. + radial_velocity : :class:`~astropy.units.Quantity`, optional, must be keyword + The radial velocity for this object along the line-of-sight. + + """ + + default_representation = coord.SphericalRepresentation + default_differential = coord.SphericalCosLatDifferential + + frame_specific_representation_info = { + coord.SphericalRepresentation: [ + coord.RepresentationMapping("lon", "phi1"), + coord.RepresentationMapping("lat", "phi2"), + coord.RepresentationMapping("distance", "distance"), + ], + } + + _default_wrap_angle = 180 * u.deg + + def __init__(self, *args, **kwargs): + wrap = kwargs.pop("wrap_longitude", True) + super().__init__(*args, **kwargs) + if wrap and isinstance( + self._data, + coord.UnitSphericalRepresentation | coord.SphericalRepresentation, + ): + self._data.lon.wrap_angle = self._default_wrap_angle + + # TODO: remove this. This is a hack required as of astropy v3.1 in order + # to have the longitude components wrap at the desired angle + def represent_as(self, base, s="base", in_frame_units=False): + r = super().represent_as(base, s=s, in_frame_units=in_frame_units) + if hasattr(r, "lon"): + r.lon.wrap_angle = self._default_wrap_angle + return r + + represent_as.__doc__ = coord.BaseCoordinateFrame.represent_as.__doc__ + + +# Rotation matrix as defined in the Appendix of Koposov et al. (2010) +R = np.array( + [ + [-0.4776303088, -0.1738432154, 0.8611897727], + [0.510844589, -0.8524449229, 0.111245042], + [0.7147776536, 0.4930681392, 0.4959603976], + ] +) + + +@frame_transform_graph.transform(coord.StaticMatrixTransform, coord.ICRS, GD1Koposov10) +def icrs_to_gd1(): + """ + Compute the transformation from ICRS spherical to heliocentric GD-1 coordinates. + """ + return R + + +@frame_transform_graph.transform(coord.StaticMatrixTransform, GD1Koposov10, coord.ICRS) +def gd1_to_icrs(): + """ + Compute the transformation from heliocentric GD-1 coordinates to ICRS spherical. + """ + return icrs_to_gd1().T diff --git a/student_download/reflex.py b/student_download/reflex.py new file mode 100644 index 00000000..cf6d2bb5 --- /dev/null +++ b/student_download/reflex.py @@ -0,0 +1,43 @@ +import astropy.coordinates as coord + +__all__ = ["reflex_correct"] + + +def reflex_correct(coords, galactocentric_frame=None): + """Correct the input Astropy coordinate object for solar reflex motion. + + The input coordinate instance must have distance and radial velocity information. + So, if the radial velocity is not known, fill the radial velocity values with zeros + to reflex-correct the proper motions. + + Parameters + ---------- + coords : `~astropy.coordinates.SkyCoord` + The Astropy coordinate object with position and velocity information. + galactocentric_frame : `~astropy.coordinates.Galactocentric` (optional) + To change properties of the Galactocentric frame, like the height of the + sun above the midplane, or the velocity of the sun in a Galactocentric + intertial frame, set arguments of the + `~astropy.coordinates.Galactocentric` object and pass in to this + function with your coordinates. + + Returns + ------- + coords : `~astropy.coordinates.SkyCoord` + The coordinates in the same frame as input, but with solar motion + removed. + + """ + c = coord.SkyCoord(coords) + + # If not specified, use the Astropy default Galactocentric frame + if galactocentric_frame is None: + galactocentric_frame = coord.Galactocentric() + + v_sun = galactocentric_frame.galcen_v_sun + + observed = c.transform_to(galactocentric_frame) + rep = observed.cartesian.without_differentials() + rep = rep.with_differentials(observed.cartesian.differentials["s"] + v_sun) + fr = galactocentric_frame.realize_frame(rep).transform_to(c.frame) + return coord.SkyCoord(fr) diff --git a/student_download/test_setup.ipynb b/student_download/test_setup.ipynb index 2e57c8a8..5572afd6 100644 --- a/student_download/test_setup.ipynb +++ b/student_download/test_setup.ipynb @@ -195,31 +195,15 @@ "metadata": {}, "outputs": [], "source": [ - "import gala.coordinates as gc" + "from gd1 import GD1Koposov10\n", + "from reflex import reflex_correct" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created TAP+ (v20200428.1) - Connection:\n", - "\tHost: gea.esac.esa.int\n", - "\tUse HTTPS: True\n", - "\tPort: 443\n", - "\tSSL Port: 443\n", - "Created TAP+ (v20200428.1) - Connection:\n", - "\tHost: gea.esac.esa.int\n", - "\tUse HTTPS: True\n", - "\tPort: 443\n", - "\tSSL Port: 443\n" - ] - } - ], + "outputs": [], "source": [ "# Note: running this import statement opens a connection\n", "# to a Gaia server, so it will fail if you are not connected\n", @@ -258,9 +242,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python (AstronomicalData)", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "astronomicaldata" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -272,7 +256,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.9.17" } }, "nbformat": 4,