astropy:docs

Source code for astropy.coordinates.distances

# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
This module contains the classes and utility functions for distance and
cartesian coordinates.
"""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import math

import numpy as np

from .. import units as u
from ..utils import deprecated

__all__ = ['Distance', 'CartesianPoints', 'cartesian_to_spherical',
           'spherical_to_cartesian']


__doctest_requires__ = {'*': ['scipy.integrate']}


[docs]class Distance(u.Quantity): """ A one-dimensional distance. This can be initialized in one of four ways: * A distance ``value`` (array or float) and a ``unit`` * A `~astropy.units.Quantity` object * A redshift and (optionally) a cosmology. * Providing a distance modulus Parameters ---------- value : scalar or `~astropy.units.Quantity`. The value of this distance. unit : `~astropy.units.UnitBase` The units for this distance, *if* ``value`` is not a `~astropy.units.Quantity`. Must have dimensions of distance. z : float A redshift for this distance. It will be converted to a distance by computing the luminosity distance for this redshift given the cosmology specified by ``cosmology``. Must be given as a keyword argument. cosmology : ``Cosmology`` or `None` A cosmology that will be used to compute the distance from ``z``. If `None`, the current cosmology will be used (see `astropy.cosmology` for details). distmod : float or `~astropy.units.Quantity` The distance modulus for this distance. Note that if ``unit`` is not provided, a guess will be made at the unit between AU, pc, kpc, and Mpc. dtype : `~numpy.dtype`, optional See `~astropy.units.Quantity`. copy : bool, optional See `~astropy.units.Quantity`. order : {'C', 'F', 'A'}, optional See `~astropy.units.Quantity`. subok : bool, optional See `~astropy.units.Quantity`. ndmin : int, optional See `~astropy.units.Quantity`. allow_negative : bool, optional Whether to allow negative distances (which are possible is some cosmologies). Default: ``False``. Raises ------ `~astropy.units.UnitsError` If the ``unit`` is not a distance. ValueError If value specified is less than 0 and ``allow_negative=False``. If ``z`` is provided with a ``unit`` or ``cosmology`` is provided when ``z`` is *not* given, or ``value`` is given as well as ``z``. Examples -------- >>> from astropy import units as u >>> from astropy import cosmology >>> from astropy.cosmology import WMAP5, WMAP7 >>> cosmology.set_current(WMAP7) >>> d1 = Distance(10, u.Mpc) >>> d2 = Distance(40, unit=u.au) >>> d3 = Distance(value=5, unit=u.kpc) >>> d4 = Distance(z=0.23) >>> d5 = Distance(z=0.23, cosmology=WMAP5) >>> d6 = Distance(distmod=24.47) >>> d7 = Distance(Distance(10 * u.Mpc)) """ _include_easy_conversion_members = True def __new__(cls, value=None, unit=None, z=None, cosmology=None, distmod=None, dtype=None, copy=True, order=None, subok=False, ndmin=0, allow_negative=False): if z is not None: if value is not None or distmod is not None: raise ValueError('Should given only one of `value`, `z` ' 'or `distmod` in Distance constructor.') if cosmology is None: from ..cosmology import default_cosmology cosmology = default_cosmology.get() value = cosmology.luminosity_distance(z) # Continue on to take account of unit and other arguments # but a copy is already made, so no longer necessary copy = False else: if cosmology is not None: raise ValueError('A `cosmology` was given but `z` was not ' 'provided in Distance constructor') if distmod is not None: if value is not None: raise ValueError('Should given only one of `value`, `z` ' 'or `distmod` in Distance constructor.') value = cls._distmod_to_pc(distmod) if unit is None: # if the unit is not specified, guess based on the mean of # the log of the distance meanlogval = np.log10(value.value).mean() if meanlogval > 6: unit = u.Mpc elif meanlogval > 3: unit = u.kpc elif meanlogval < -3: #~200 AU unit = u.AU else: unit = u.pc # Continue on to take account of unit and other arguments # but a copy is already made, so no longer necessary copy = False elif value is None: raise ValueError('None of `value`, `z`, or `distmod` were ' 'given to Distance constructor') # now we have arguments like for a Quantity, so let it do the work distance = super(Distance, cls).__new__( cls, value, unit, dtype=dtype, copy=copy, order=order, subok=subok, ndmin=ndmin) if not distance.unit.is_equivalent(u.m): raise u.UnitsError('Unit "{0}" is not a length type'.format(unit)) if not allow_negative and np.any(distance.value < 0): raise ValueError("Distance must be >= 0. Use the argument " "'allow_negative=True' to allow negative values.") return distance def __quantity_subclass__(self, unit): if unit.is_equivalent(u.m): return Distance, True else: return super(Distance, self).__quantity_subclass__(unit)[0], False @property def z(self): """Short for ``self.compute_z()``""" return self.compute_z()
[docs] def compute_z(self, cosmology=None): """ The redshift for this distance assuming its physical distance is a luminosity distance. Parameters ---------- cosmology : ``Cosmology`` or `None` The cosmology to assume for this calculation, or `None` to use the current cosmology (see `astropy.cosmology` for details). Returns ------- z : float The redshift of this distance given the provided ``cosmology``. """ from ..cosmology import luminosity_distance from scipy import optimize # FIXME: array: need to make this calculation more vector-friendly f = lambda z, d, cos: (luminosity_distance(z, cos).value - d) ** 2 return optimize.brent(f, (self.Mpc, cosmology))
@property def distmod(self): """The distance modulus as a `~astropy.units.Quantity`""" val = 5. * np.log10(self.to(u.pc).value) - 5. return u.Quantity(val, u.mag) @classmethod def _distmod_to_pc(cls, dm): dm = u.Quantity(dm, u.mag) return cls(10 ** ((dm.value + 5) / 5.), u.pc, copy=False)
@deprecated('v0.4', alternative='astropy.coordinates.CartesianRepresentation')
[docs]class CartesianPoints(u.Quantity): """ A cartesian representation of a point in three-dimensional space. Parameters ---------- x : `~astropy.units.Quantity` or array-like The first cartesian coordinate or a single array or `~astropy.units.Quantity` where the first dimension is length-3. y : `~astropy.units.Quantity` or array-like, optional The second cartesian coordinate. z : `~astropy.units.Quantity` or array-like, optional The third cartesian coordinate. unit : `~astropy.units.UnitBase` object or `None` The physical unit of the coordinate values. If ``x``, ``y``, or ``z`` are quantities, they will be converted to this unit. dtype : `~numpy.dtype`, optional See `~astropy.units.Quantity`. Must be given as a keyword argument. copy : bool, optional See `~astropy.units.Quantity`. Must be given as a keyword argument. Raises ------ UnitsError If the units on ``x``, ``y``, and ``z`` do not match or an invalid unit is given. ValueError If ``y`` and ``z`` don't match ``x``'s shape or ``x`` is not length-3 TypeError If incompatible array types are passed into ``x``, ``y``, or ``z`` """ #this ensures that __array_wrap__ gets called for ufuncs even when #where a quantity is first, like ``3*u.m + c`` __array_priority__ = 10001 def __new__(cls, x, y=None, z=None, unit=None, dtype=None, copy=True): if y is None and z is None: if len(x) != 3: raise ValueError('Input to CartesianPoints is not length 3') qarr = x if unit is None and hasattr(qarr, 'unit'): unit = qarr.unit # for when a Quantity is given elif y is not None and z is not None: if unit is None: #they must all match units or this fails for coo in (x, y, z): if hasattr(coo, 'unit'): if unit is not None and coo.unit != unit: raise u.UnitsError('Units for `x`, `y`, and `z` do ' 'not match in CartesianPoints ') unit = coo.unit #if `unit` is still None at this point, it means none were #Quantties, which is fine, because it means the user wanted #the unit to be None else: #convert any that are like a Quantity to the given unit if hasattr(x, 'to'): x = x.to(unit) if hasattr(y, 'to'): y = y.to(unit) if hasattr(z, 'to'): z = z.to(unit) qarr = [np.asarray(coo) for coo in (x, y, z)] if not (qarr[0].shape == qarr[1].shape == qarr[2].shape): raise ValueError("Shapes for `x`, `y`, and `z` don't match in " "CartesianPoints") #let the unit be whatever it is else: raise TypeError('Must give all of `x`, `y`, and `z` or just array in ' 'CartesianPoints') try: unit = _convert_to_and_validate_length_unit(unit, True) except TypeError as e: raise u.UnitsError(str(e)) try: qarr = np.asarray(qarr) except ValueError as e: raise TypeError(str(e)) if qarr.dtype.kind not in 'iuf': raise TypeError("Unsupported dtype '{0}'".format(qarr.dtype)) return u.Quantity.__new__(cls, qarr, unit, dtype=dtype, copy=copy) def __quantity_subclass__(self, unit): if unit.is_equivalent(u.m): return CartesianPoints, True else: return u.Quantity.__quantity_subclass__(unit)[0], False def __array_wrap__(self, obj, context=None): #always convert to CartesianPoints because all operations that would #screw up the units are killed by _convert_to_and_validate_length_unit obj = u.Quantity.__array_wrap__(obj, context=context) #always prefer self's unit, if possible if obj.unit.is_equivalent(self.unit): return obj.to(self.unit) else: return obj @property def x(self): """ The second cartesian coordinate as a `~astropy.units.Quantity`. """ return self.view(u.Quantity)[0] @property def y(self): """ The second cartesian coordinate as a `~astropy.units.Quantity`. """ return self.view(u.Quantity)[1] @property def z(self): """ The third cartesian coordinate as a `~astropy.units.Quantity`. """ return self.view(u.Quantity)[2] def to_spherical(self): """ Converts to the spherical representation of this point. Returns ------- r : `~astropy.units.Quantity` The radial coordinate (in the same units as this `CartesianPoints`). lat : `~astropy.units.Quantity` The spherical coordinates latitude. lon : `~astropy.units.Quantity` The spherical coordinates longitude. """ from .angles import Latitude, Longitude rarr, latarr, lonarr = cartesian_to_spherical(self.x, self.y, self.z) r = Distance(rarr, unit=self.unit) lat = Latitude(latarr, unit=u.radian) lon = Longitude(lonarr, unit=u.radian) return r, lat, lon
def _convert_to_and_validate_length_unit(unit, allow_dimensionless=False): """ raises UnitsError if not a length unit """ try: unit = u.Unit(unit) assert (unit.is_equivalent(u.kpc) or allow_dimensionless and unit == u.dimensionless_unscaled) except (TypeError, AssertionError): raise u.UnitsError('Unit "{0}" is not a length type'.format(unit)) return unit #<------------transformation-related utility functions----------------->
[docs]def cartesian_to_spherical(x, y, z): """ Converts 3D rectangular cartesian coordinates to spherical polar coordinates. Note that the resulting angles are latitude/longitude or elevation/azimuthal form. I.e., the origin is along the equator rather than at the north pole. .. note:: This is a low-level function used internally in `astropy.coordinates`. It is provided for users if they really want to use it, but it is recommended that you use the `astropy.coordinates` coordinate systems. Parameters ---------- x : scalar or array-like The first cartesian coordinate. y : scalar or array-like The second cartesian coordinate. z : scalar or array-like The third cartesian coordinate. Returns ------- r : float or array The radial coordinate (in the same units as the inputs). lat : float or array The latitude in radians lon : float or array The longitude in radians """ xsq = x ** 2 ysq = y ** 2 zsq = z ** 2 r = (xsq + ysq + zsq) ** 0.5 s = (xsq + ysq) ** 0.5 if np.isscalar(x) and np.isscalar(y) and np.isscalar(z): lon = math.atan2(y, x) lat = math.atan2(z, s) else: lon = np.arctan2(y, x) lat = np.arctan2(z, s) return r, lat, lon
[docs]def spherical_to_cartesian(r, lat, lon): """ Converts spherical polar coordinates to rectangular cartesian coordinates. Note that the input angles should be in latitude/longitude or elevation/azimuthal form. I.e., the origin is along the equator rather than at the north pole. .. note:: This is a low-level function used internally in `astropy.coordinates`. It is provided for users if they really want to use it, but it is recommended that you use the `astropy.coordinates` coordinate systems. Parameters ---------- r : scalar or array-like The radial coordinate (in the same units as the inputs). lat : scalar or array-like The latitude in radians lon : scalar or array-like The longitude in radians Returns ------- x : float or array The first cartesian coordinate. y : float or array The second cartesian coordinate. z : float or array The third cartesian coordinate. """ if np.isscalar(r) and np.isscalar(lat) and np.isscalar(lon): x = r * math.cos(lat) * math.cos(lon) y = r * math.cos(lat) * math.sin(lon) z = r * math.sin(lat) else: x = r * np.cos(lat) * np.cos(lon) y = r * np.cos(lat) * np.sin(lon) z = r * np.sin(lat) return x, y, z

Page Contents