{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n==========================================================\nCreate a new coordinate class (for the Sagittarius stream)\n==========================================================\n\nThis document describes in detail how to subclass and define a custom spherical\ncoordinate frame, as discussed in `astropy-coordinates-design` and the\ndocstring for `~astropy.coordinates.BaseCoordinateFrame`. In this example, we\nwill define a coordinate system defined by the plane of orbit of the Sagittarius\nDwarf Galaxy (hereafter Sgr; as defined in Majewski et al. 2003). The Sgr\ncoordinate system is often referred to in terms of two angular coordinates,\n$\\Lambda,B$.\n\nTo do this, we need to define a subclass of\n`~astropy.coordinates.BaseCoordinateFrame` that knows the names and units of the\ncoordinate system angles in each of the supported representations. In this case\nwe support `~astropy.coordinates.SphericalRepresentation` with \"Lambda\" and\n\"Beta\". Then we have to define the transformation from this coordinate system to\nsome other built-in system. Here we will use Galactic coordinates, represented\nby the `~astropy.coordinates.Galactic` class.\n\nSee Also\n--------\n\n* The `gala package `_, which defines a number of\n Astropy coordinate frames for stellar stream coordinate systems.\n* Majewski et al. 2003, \"A Two Micron All Sky Survey View of the Sagittarius\n Dwarf Galaxy. I. Morphology of the Sagittarius Core and Tidal Arms\",\n https://arxiv.org/abs/astro-ph/0304198\n* Law & Majewski 2010, \"The Sagittarius Dwarf Galaxy: A Model for Evolution in a\n Triaxial Milky Way Halo\", https://arxiv.org/abs/1003.1132\n* David Law's Sgr info page http://www.stsci.edu/~dlaw/Sgr/\n\n-------------------\n\n*By: Adrian Price-Whelan, Erik Tollerud*\n\n*License: BSD*\n\n-------------------\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make `print` work the same in all versions of Python, set up numpy,\nmatplotlib, and use a nicer set of plot parameters:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nimport matplotlib.pyplot as plt\nfrom astropy.visualization import astropy_mpl_style\nplt.style.use(astropy_mpl_style)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the packages necessary for coordinates\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from astropy.coordinates import frame_transform_graph\nfrom astropy.coordinates.matrix_utilities import rotation_matrix, matrix_product, matrix_transpose\nimport astropy.coordinates as coord\nimport astropy.units as u" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first step is to create a new class, which we'll call\n``Sagittarius`` and make it a subclass of\n`~astropy.coordinates.BaseCoordinateFrame`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Sagittarius(coord.BaseCoordinateFrame):\n \"\"\"\n A Heliocentric spherical coordinate system defined by the orbit\n of the Sagittarius dwarf galaxy, as described in\n http://adsabs.harvard.edu/abs/2003ApJ...599.1082M\n and further explained in\n http://www.stsci.edu/~dlaw/Sgr/.\n\n Parameters\n ----------\n representation : `~astropy.coordinates.BaseRepresentation` or None\n A representation object or None to have no data (or use the other keywords)\n Lambda : `~astropy.coordinates.Angle`, optional, must be keyword\n The longitude-like angle corresponding to Sagittarius' orbit.\n Beta : `~astropy.coordinates.Angle`, optional, must be keyword\n The latitude-like angle corresponding to Sagittarius' orbit.\n distance : `Quantity`, optional, must be keyword\n The Distance for this object along the line-of-sight.\n pm_Lambda_cosBeta : :class:`~astropy.units.Quantity`, optional, must be keyword\n The proper motion along the stream in ``Lambda`` (including the\n ``cos(Beta)`` factor) for this object (``pm_Beta`` must also be given).\n pm_Beta : :class:`~astropy.units.Quantity`, optional, must be keyword\n The proper motion in Declination for this object (``pm_ra_cosdec`` must\n also be given).\n radial_velocity : :class:`~astropy.units.Quantity`, optional, must be keyword\n The radial velocity of this object.\n\n \"\"\"\n\n default_representation = coord.SphericalRepresentation\n default_differential = coord.SphericalCosLatDifferential\n\n frame_specific_representation_info = {\n coord.SphericalRepresentation: [\n coord.RepresentationMapping('lon', 'Lambda'),\n coord.RepresentationMapping('lat', 'Beta'),\n coord.RepresentationMapping('distance', 'distance')]\n }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Breaking this down line-by-line, we define the class as a subclass of\n`~astropy.coordinates.BaseCoordinateFrame`. Then we include a descriptive\ndocstring. The final lines are class-level attributes that specify the\ndefault representation for the data, default differential for the velocity\ninformation, and mappings from the attribute names used by representation\nobjects to the names that are to be used by the ``Sagittarius`` frame. In this\ncase we override the names in the spherical representations but don't do\nanything with other representations like cartesian or cylindrical.\n\nNext we have to define the transformation from this coordinate system to some\nother built-in coordinate system; we will use Galactic coordinates. We can do\nthis by defining functions that return transformation matrices, or by simply\ndefining a function that accepts a coordinate and returns a new coordinate in\nthe new system. Because the transformation to the Sagittarius coordinate\nsystem is just a spherical rotation from Galactic coordinates, we'll just\ndefine a function that returns this matrix. We'll start by constructing the\ntransformation matrix using pre-determined Euler angles and the\n``rotation_matrix`` helper function:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "SGR_PHI = (180 + 3.75) * u.degree # Euler angles (from Law & Majewski 2010)\nSGR_THETA = (90 - 13.46) * u.degree\nSGR_PSI = (180 + 14.111534) * u.degree\n\n# Generate the rotation matrix using the x-convention (see Goldstein)\nD = rotation_matrix(SGR_PHI, \"z\")\nC = rotation_matrix(SGR_THETA, \"x\")\nB = rotation_matrix(SGR_PSI, \"z\")\nA = np.diag([1.,1.,-1.])\nSGR_MATRIX = matrix_product(A, B, C, D)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we already constructed the transformation (rotation) matrix above, and\nthe inverse of a rotation matrix is just its transpose, the required\ntransformation functions are very simple:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@frame_transform_graph.transform(coord.StaticMatrixTransform, coord.Galactic, Sagittarius)\ndef galactic_to_sgr():\n \"\"\" Compute the transformation matrix from Galactic spherical to\n heliocentric Sgr coordinates.\n \"\"\"\n return SGR_MATRIX" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The decorator ``@frame_transform_graph.transform(coord.StaticMatrixTransform,\ncoord.Galactic, Sagittarius)`` registers this function on the\n``frame_transform_graph`` as a coordinate transformation. Inside the function,\nwe simply return the previously defined rotation matrix.\n\nWe then register the inverse transformation by using the transpose of the\nrotation matrix (which is faster to compute than the inverse):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@frame_transform_graph.transform(coord.StaticMatrixTransform, Sagittarius, coord.Galactic)\ndef sgr_to_galactic():\n \"\"\" Compute the transformation matrix from heliocentric Sgr coordinates to\n spherical Galactic.\n \"\"\"\n return matrix_transpose(SGR_MATRIX)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we've registered these transformations between ``Sagittarius`` and\n`~astropy.coordinates.Galactic`, we can transform between *any* coordinate\nsystem and ``Sagittarius`` (as long as the other system has a path to\ntransform to `~astropy.coordinates.Galactic`). For example, to transform from\nICRS coordinates to ``Sagittarius``, we would do:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "icrs = coord.ICRS(280.161732*u.degree, 11.91934*u.degree)\nsgr = icrs.transform_to(Sagittarius)\nprint(sgr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, to transform from the ``Sagittarius`` frame to ICRS coordinates (in this\ncase, a line along the ``Sagittarius`` x-y plane):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "sgr = Sagittarius(Lambda=np.linspace(0, 2*np.pi, 128)*u.radian,\n Beta=np.zeros(128)*u.radian)\nicrs = sgr.transform_to(coord.ICRS)\nprint(icrs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As an example, we'll now plot the points in both coordinate systems:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(2, 1, figsize=(8, 10),\n subplot_kw={'projection': 'aitoff'})\n\naxes[0].set_title(\"Sagittarius\")\naxes[0].plot(sgr.Lambda.wrap_at(180*u.deg).radian, sgr.Beta.radian,\n linestyle='none', marker='.')\n\naxes[1].set_title(\"ICRS\")\naxes[1].plot(icrs.ra.wrap_at(180*u.deg).radian, icrs.dec.radian,\n linestyle='none', marker='.')\n\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This particular transformation is just a spherical rotation, which is a\nspecial case of an Affine transformation with no vector offset. The\ntransformation of velocity components is therefore natively supported as\nwell:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "sgr = Sagittarius(Lambda=np.linspace(0, 2*np.pi, 128)*u.radian,\n Beta=np.zeros(128)*u.radian,\n pm_Lambda_cosBeta=np.random.uniform(-5, 5, 128)*u.mas/u.yr,\n pm_Beta=np.zeros(128)*u.mas/u.yr)\nicrs = sgr.transform_to(coord.ICRS)\nprint(icrs)\n\nfig, axes = plt.subplots(3, 1, figsize=(8, 10), sharex=True)\n\naxes[0].set_title(\"Sagittarius\")\naxes[0].plot(sgr.Lambda.degree,\n sgr.pm_Lambda_cosBeta.value,\n linestyle='none', marker='.')\naxes[0].set_xlabel(r\"$\\Lambda$ [deg]\")\naxes[0].set_ylabel(r\"$\\mu_\\Lambda \\, \\cos B$ [{0}]\"\n .format(sgr.pm_Lambda_cosBeta.unit.to_string('latex_inline')))\n\naxes[1].set_title(\"ICRS\")\naxes[1].plot(icrs.ra.degree, icrs.pm_ra_cosdec.value,\n linestyle='none', marker='.')\naxes[1].set_ylabel(r\"$\\mu_\\alpha \\, \\cos\\delta$ [{0}]\"\n .format(icrs.pm_ra_cosdec.unit.to_string('latex_inline')))\n\naxes[2].set_title(\"ICRS\")\naxes[2].plot(icrs.ra.degree, icrs.pm_dec.value,\n linestyle='none', marker='.')\naxes[2].set_xlabel(\"RA [deg]\")\naxes[2].set_ylabel(r\"$\\mu_\\delta$ [{0}]\"\n .format(icrs.pm_dec.unit.to_string('latex_inline')))\n\nplt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.7" } }, "nbformat": 4, "nbformat_minor": 0 }