Skip to content
This repository
Browse code

Merge branch 'master' into erfa

Conflicts:
	CHANGES.rst
  • Loading branch information...
commit 8fb6e1dd046eff198f508a933c251756be016830 2 parents 93a9ae0 + c9b52ba
Sergio authored August 11, 2013

Showing 194 changed files with 8,694 additions and 3,033 deletions. Show diff stats Hide diff stats

  1. 4  .travis.yml
  2. 190  CHANGES.rst
  3. 27  astropy/__init__.py
  4. 7  astropy/constants/constant.py
  5. 4  astropy/constants/si.py
  6. 19  astropy/constants/tests/test_constant.py
  7. 2  astropy/coordinates/angle_lextab.py
  8. 62  astropy/coordinates/angle_parsetab.py
  9. 172  astropy/coordinates/angle_utilities.py
  10. 875  astropy/coordinates/angles.py
  11. 36  astropy/coordinates/builtin_systems.py
  12. 25  astropy/coordinates/coordsystems.py
  13. 7  astropy/coordinates/distances.py
  14. 4  astropy/coordinates/tests/accuracy/test_fk4_no_e_fk4.py
  15. 4  astropy/coordinates/tests/accuracy/test_fk4_no_e_fk5.py
  16. 4  astropy/coordinates/tests/accuracy/test_galactic_fk4.py
  17. 4  astropy/coordinates/tests/accuracy/test_icrs_fk5.py
  18. 83  astropy/coordinates/tests/test_angles.py
  19. 174  astropy/coordinates/tests/test_api.py
  20. 79  astropy/coordinates/tests/test_arrays.py
  21. 81  astropy/coordinates/tests/test_formatting.py
  22. 22  astropy/coordinates/tests/test_name_resolve.py
  23. 4  astropy/coordinates/tests/test_sphere_ang_dists.py
  24. 48  astropy/coordinates/tests/test_transformations.py
  25. 270  astropy/cosmology/core.py
  26. 24  astropy/cosmology/funcs.py
  27. 2  astropy/cosmology/parameters.py
  28. 181  astropy/cosmology/tests/test_cosmology.py
  29. 4  astropy/extern/setup_package.py
  30. 404  astropy/extern/six.py
  31. 2  astropy/io/ascii/fixedwidth.py
  32. 2  astropy/io/ascii/tests/common.py
  33. 1  astropy/io/fits/hdu/compressed.py
  34. 2  astropy/io/fits/hdu/groups.py
  35. 4  astropy/io/fits/header.py
  36. 2  astropy/io/fits/scripts/fitscheck.py
  37. 3  astropy/io/fits/src/compressionmodule.c
  38. 7  astropy/io/fits/tests/test_core.py
  39. 28  astropy/io/fits/util.py
  40. 2  astropy/io/votable/__init__.py
  41. 12  astropy/io/votable/connect.py
  42. 106  astropy/io/votable/converters.py
  43. 42  astropy/io/votable/exceptions.py
  44. 4  astropy/io/votable/setup_package.py
  45. 27  astropy/io/votable/table.py
  46. 11  astropy/io/votable/tests/converter_test.py
  47. 9  astropy/io/votable/tests/ucd_test.py
  48. 6  astropy/io/votable/tests/util_test.py
  49. 96  astropy/io/votable/tests/vo_test.py
  50. 185  astropy/io/votable/tree.py
  51. 26  astropy/io/votable/ucd.py
  52. 34  astropy/io/votable/util.py
  53. 2  astropy/io/votable/validator/__init__.py
  54. 164  astropy/io/votable/validator/html.py
  55. 5  astropy/io/votable/validator/main.py
  56. 43  astropy/io/votable/validator/result.py
  57. 1  astropy/io/votable/volint.py
  58. 2  astropy/io/votable/xmlutil.py
  59. 236  astropy/modeling/constraints.py
  60. 226  astropy/modeling/core.py
  61. 171  astropy/modeling/fitting.py
  62. 779  astropy/modeling/functional_models.py
  63. 88  astropy/modeling/models.py
  64. 120  astropy/modeling/parameters.py
  65. 11  astropy/modeling/polynomial.py
  66. 84  astropy/modeling/projections.py
  67. 59  astropy/modeling/rotations.py
  68. 2  astropy/modeling/setup_package.py
  69. 161  astropy/modeling/tests/model_lists.py
  70. 198  astropy/modeling/tests/test_constraints.py
  71. 5  astropy/modeling/tests/test_input.py
  72. 162  astropy/modeling/tests/test_models.py
  73. 6  astropy/modeling/tests/test_parameters.py
  74. 144  astropy/modeling/tests/test_projections.py
  75. 1  astropy/modeling/utils.py
  76. 159  astropy/setup_helpers.py
  77. 9  astropy/tests/tests/test_imports.py
  78. 195  astropy/time/core.py
  79. 1  astropy/time/setup_package.py
  80. 42  astropy/time/tests/test_basic.py
  81. 8  astropy/time/tests/test_comparisons.py
  82. 17  astropy/time/tests/test_delta.py
  83. 101  astropy/time/tests/test_precision.py
  84. 174  astropy/units/core.py
  85. 207  astropy/units/equivalencies.py
  86. 2  astropy/units/format/cds.py
  87. 3  astropy/units/format/console.py
  88. 13  astropy/units/format/generic.py
  89. 3  astropy/units/format/latex.py
  90. 6  astropy/units/format/unicode_format.py
  91. 20  astropy/units/format/utils.py
  92. 828  astropy/units/quantity.py
  93. 294  astropy/units/quantity_helper.py
  94. 68  astropy/units/tests/test_equivalencies.py
  95. 12  astropy/units/tests/test_format.py
  96. 149  astropy/units/tests/test_quantity.py
  97. 365  astropy/units/tests/test_quantity_array_methods.py
  98. 25  astropy/units/tests/test_quantity_non_ufuncs.py
  99. 579  astropy/units/tests/test_quantity_ufuncs.py
  100. 12  astropy/units/tests/test_units.py
  101. 21  astropy/utils/compat/misc.py
  102. 13  astropy/utils/console.py
  103. 4  astropy/version_helpers.py
  104. 7  astropy/wcs/src/pyutil.c
  105. 3  astropy/wcs/src/str_list_proxy.c
  106. 6  astropy/wcs/src/wcslib_wrap.c
  107. 32  astropy/wcs/tests/test_wcs.py
  108. 93  astropy/wcs/wcs.py
  109. 6  cextern/wcslib/C/GNUmakefile
  110. 4  cextern/wcslib/C/cel.c
  111. 6  cextern/wcslib/C/cel.h
  112. 4  cextern/wcslib/C/fitshdr.h
  113. 4  cextern/wcslib/C/fitshdr.l
  114. 4  cextern/wcslib/C/flexed/fitshdr.c
  115. 4  cextern/wcslib/C/flexed/wcsbth.c
  116. 4  cextern/wcslib/C/flexed/wcspih.c
  117. 4  cextern/wcslib/C/flexed/wcsulex.c
  118. 4  cextern/wcslib/C/flexed/wcsutrn.c
  119. 4  cextern/wcslib/C/getwcstab.c
  120. 4  cextern/wcslib/C/getwcstab.h
  121. 4  cextern/wcslib/C/lin.c
  122. 6  cextern/wcslib/C/lin.h
  123. 4  cextern/wcslib/C/log.c
  124. 6  cextern/wcslib/C/log.h
  125. 240  cextern/wcslib/C/makedefs.in
  126. 334  cextern/wcslib/C/prj.c
  127. 13  cextern/wcslib/C/prj.h
  128. 4  cextern/wcslib/C/spc.c
  129. 6  cextern/wcslib/C/spc.h
  130. 4  cextern/wcslib/C/sph.c
  131. 6  cextern/wcslib/C/sph.h
  132. 4  cextern/wcslib/C/spx.c
  133. 6  cextern/wcslib/C/spx.h
  134. 4  cextern/wcslib/C/tab.c
  135. 6  cextern/wcslib/C/tab.h
  136. 4  cextern/wcslib/C/wcs.c
  137. 6  cextern/wcslib/C/wcs.h
  138. 4  cextern/wcslib/C/wcsbth.l
  139. 18  cextern/wcslib/C/wcsconfig.h.in
  140. 18  cextern/wcslib/C/wcsconfig_tests.h.in
  141. 4  cextern/wcslib/C/wcserr.c
  142. 4  cextern/wcslib/C/wcserr.h
  143. 6  cextern/wcslib/C/wcsfix.c
  144. 15  cextern/wcslib/C/wcsfix.h
  145. 4  cextern/wcslib/C/wcshdr.c
  146. 6  cextern/wcslib/C/wcshdr.h
  147. 6  cextern/wcslib/C/wcslib.h
  148. 4  cextern/wcslib/C/wcsmath.h
  149. 4  cextern/wcslib/C/wcspih.l
  150. 4  cextern/wcslib/C/wcsprintf.c
  151. 6  cextern/wcslib/C/wcsprintf.h
  152. 4  cextern/wcslib/C/wcstrig.c
  153. 4  cextern/wcslib/C/wcstrig.h
  154. 4  cextern/wcslib/C/wcsulex.l
  155. 4  cextern/wcslib/C/wcsunits.c
  156. 6  cextern/wcslib/C/wcsunits.h
  157. 4  cextern/wcslib/C/wcsutil.c
  158. 4  cextern/wcslib/C/wcsutil.h
  159. 4  cextern/wcslib/C/wcsutrn.l
  160. 36  cextern/wcslib/CHANGES
  161. 8  cextern/wcslib/GNUmakefile
  162. 8  cextern/wcslib/INSTALL
  163. 6  cextern/wcslib/README
  164. 3  cextern/wcslib/THANKS
  165. 11  cextern/wcslib/VALIDATION
  166. 24  cextern/wcslib/configure
  167. 6  cextern/wcslib/configure.ac
  168. 2  cextern/wcslib/flavours
  169. 8  cextern/wcslib/makedefs.in
  170. 4  cextern/wcslib/wcsconfig.h.in
  171. 4  cextern/wcslib/wcsconfig_f77.h.in
  172. 4  cextern/wcslib/wcsconfig_tests.h.in
  173. 4  cextern/wcslib/wcsconfig_utils.h.in
  174. 8  distribute_setup.py
  175. 64  docs/coordinates/angles.rst
  176. 25  docs/coordinates/creating.rst
  177. 10  docs/coordinates/formatting.rst
  178. 68  docs/coordinates/index.rst
  179. 13  docs/coordinates/separations.rst
  180. 36  docs/coordinates/sgr-example.py
  181. 202  docs/coordinates/sgr-example.rst
  182. 31  docs/coordinates/transforming.rst
  183. 39  docs/cosmology/index.rst
  184. 199  docs/development/workflow/development_workflow.rst
  185. 26  docs/install.rst
  186. 10  docs/modeling/fitting.rst
  187. 23  docs/modeling/index.rst
  188. 56  docs/modeling/new.rst
  189. 8  docs/time/index.rst
  190. 48  docs/units/equivalencies.rst
  191. 209  docs/units/quantity.rst
  192. 51  docs/whatsnew/0.3.rst
  193. 18  licenses/SIX_LICENSE.rst
  194. 35  setup.py
4  .travis.yml
@@ -35,9 +35,9 @@ matrix:
35 35
 
36 36
         # now try do scipy on 2.7 and an appropriate 3.x build (with latest numpy)
37 37
         - python: 2.7
38  
-          env: SETUP_CMD='test --parallel=8' NUMPY_VERSION=1.7.1 OPTIONAL_DEPS=true
  38
+          env: SETUP_CMD='test --parallel=8' NUMPY_VERSION=1.7.1 OPTIONAL_DEPS=true LC_CTYPE=C.ascii
39 39
         - python: 3.2
40  
-          env: SETUP_CMD='test' NUMPY_VERSION=1.7.1 OPTIONAL_DEPS=true
  40
+          env: SETUP_CMD='test' NUMPY_VERSION=1.7.1 OPTIONAL_DEPS=true LC_CTYPE=C.ascii
41 41
 
42 42
         # try alternate numpy versions
43 43
         - python: 2.7
190  CHANGES.rst
Source Rendered
@@ -4,7 +4,7 @@
4 4
 New Features
5 5
 ^^^^^^^^^^^^
6 6
 
7  
-- `astropy.io.votable`
  7
+- ``astropy.io.votable``
8 8
 
9 9
   - The format of the units of a VOTable file can be specified using
10 10
     the `unit_format` parameter.  Note that units are still always
@@ -13,6 +13,10 @@ New Features
13 13
 
14 14
 - ``astropy.time``
15 15
 
  16
+  - Update internal time manipulations so that arithmetic with Time and
  17
+    TimeDelta objects maintains sub-nanosecond precision over a time span
  18
+    longer than the age of the universe [#1189].
  19
+  
16 20
   - Add ``datetime`` format which allows converting to and from standard
17 21
     library ``datetime.datetime`` objects.
18 22
 
@@ -31,7 +35,7 @@ New Features
31 35
   - Allow multiplication and division of TimeDelta objects by
32 36
     constants and arrays, as well as changing sign (negation) and
33 37
     taking the absolute value of TimeDelta objects [#1082].
34  
-    
  38
+
35 39
   - Allow comparisons of Time and TimeDelta objects [#1171].
36 40
 
37 41
 - ``astropy.stats``
@@ -52,17 +56,55 @@ New Features
52 56
     MaskedColumn, Row, and Table are now deprecated in favor of the
53 57
     single-tense 'unit' and 'dtype' [#1174].
54 58
 
55  
-- :ref:`astropy.vo <astropy_vo>`
  59
+- ``astropy.vo``
56 60
 
57 61
   - New package added to support Virtual Observatory Simple Cone Search query
58 62
     and service validation [#552].
59 63
 
  64
+- ``astropy.units.equivalencies``
  65
+
  66
+  - Added new spectroscopic equivalencies for velocity conversions
  67
+    (relativistic, optical, and radio conventions are supported)
  68
+
60 69
 - Astropy now uses the free software library ERFA in place of the 
61 70
   non-free library SOFA [#1195].
62 71
 
63 72
 API Changes
64 73
 ^^^^^^^^^^^
65 74
 
  75
+- ``astropy.coordinates``
  76
+
  77
+  - The `astropy.coordinates.Angle` class is now a subclass of
  78
+    `astropy.units.Quantity`.
  79
+
  80
+    - All angular units are now supported, not just `radian`, `degree`
  81
+      and `hour`, but now `arcsecond` and `arcminute` as well.  The
  82
+      object will retain its native unit, so when printing out a value
  83
+      initially provided in hours, its `to_string()` will, by default,
  84
+      also be expressed in hours.
  85
+
  86
+    - The `Angle` class now supports arrays of angles.
  87
+
  88
+    - To be consistent with `units.Unit`, `Angle.format` has been
  89
+      deprecated and renamed to `Angle.to_string`.
  90
+
  91
+    - To be consistent with `astropy.units`, all plural forms of unit
  92
+      names have been removed.  Therefore, the following properties of
  93
+      `astropy.coordinates.Angle` should be renamed:
  94
+
  95
+      - ``radians`` -> ``radian``
  96
+      - ``degrees`` -> ``degree``
  97
+      - ``hours`` -> ``hour``
  98
+
  99
+    - Multiplication and division of two `Angle` objects used to raise
  100
+      `NotImplementedError`.  Now they raise `TypeError`.
  101
+
  102
+  - `astropy.coordinates.angles.rotation_matrix` and
  103
+    `astropy.coordinates.angles.angle_axis` now take a `unit` kwarg
  104
+    instead of `degrees` kwarg to specify the units of the angles.
  105
+    `rotation_matrix` will also take the unit from the given `Angle`
  106
+    object if no unit is provided.
  107
+
66 108
 - ``astropy.io.ascii``
67 109
 
68 110
   - In the ``read`` method of ``astropy.io.ascii``, empty column values in an ASCII table
@@ -70,7 +112,7 @@ API Changes
70 112
     string "".  This now corresponds to the behavior of other table readers like
71 113
     ``numpy.genfromtxt``.  To restore the previous behavior set ``fill_values=None`` in the
72 114
     call to ``ascii.read()``.
73  
-    
  115
+
74 116
   - The ``read`` and ``write`` methods of ``astropy.io.ascii`` now have a ``format``
75 117
     argument for specifying the file format.  This is the preferred way to choose
76 118
     the format instead of the ``Reader`` and ``Writer`` arguments [#961].
@@ -125,19 +167,148 @@ API Changes
125 167
     the first argument is now ``order='C'``.  This is required for compatibility
126 168
     with Numpy 1.8 which is currently in development [#1250].
127 169
 
  170
+- ``astropy.units``
  171
+
  172
+  - The ``Quantity`` class now inherits from the Numpy array class, and
  173
+    includes the following API changes [#929]:
  174
+
  175
+    - Using ``float(...)``, ``int(...)``, and ``long(...)`` on a quantity will
  176
+      now only work if the quantity is dimensionless and unscaled.
  177
+
  178
+    - All Numpy ufuncs should now treat units correctly (or raise an exception
  179
+      if not supported), rather than extract the value of quantities and
  180
+      operate on this, emitting a warning about the implicit loss of units.
  181
+
  182
+    - When using relevant Numpy ufuncs on dimensionless quantities (e.g.
  183
+      ``np.exp(h * nu / (k_B * T))``), or combining dimensionless quantities
  184
+      with Python scalars or plain Numpy arrays ``1 + v / c``, the
  185
+      dimensionless Quantity will automatically be converted to an unscaled
  186
+      dimensionless Quantity.
  187
+
  188
+    - When initializing a quantity from a value with no unit, it is now set to
  189
+      be dimensionless and unscaled by default. When initializing a Quantity
  190
+      from another Quantity and with no unit specified in the initializer, the
  191
+      unit is now taken from the unit of the Quantity being initialized from.
  192
+
128 193
 Bug Fixes
129 194
 ^^^^^^^^^^
130 195
 
131 196
 - ``astropy.io.ascii``
132 197
 
133  
-  - The ``write()`` function was ignoring the ``fill_values`` argument (#910).
  198
+  - The ``write()`` function was ignoring the ``fill_values`` argument [#910].
  199
+
  200
+- ``astropy.units``
  201
+
  202
+  - Fixed a bug that caused the order of multiplication/division of plain
  203
+    Numpy arrays with Quantities to matter (i.e. if the plain array comes
  204
+    first the units were not preserved in the output) [#899].
  205
+
  206
+
  207
+0.2.5 (unreleased)
  208
+------------------
  209
+
  210
+Bug Fixes
  211
+^^^^^^^^^
  212
+
  213
+- ``astropy.io.fits``
134 214
 
  215
+  - Fixed a bug that could cause a segfault when trying to decompress an
  216
+    compressed HDU whose contents are truncated (due to a corrupt file, for
  217
+    example). This still causes a Python traceback but better that than a
  218
+    segfault. [#1332]
135 219
 
136 220
 
137  
-0.2.4 (unreleased)
  221
+0.2.4 (2013-07-24)
138 222
 ------------------
139 223
 
140  
- - Nothing yet.
  224
+Bug Fixes
  225
+^^^^^^^^^
  226
+
  227
+- ``astropy.coordinates``
  228
+
  229
+  - Fixed the angle parser to support parsing the string "1 degree". [#1168]
  230
+
  231
+- ``astropy.cosmology``
  232
+
  233
+  - Fixed a crash in the ``comoving_volume`` method on non-flat cosmologies
  234
+    when passing it an array of redshifts.
  235
+
  236
+- ``astropy.io.ascii``
  237
+
  238
+  - Fixed a bug that prevented saving changes to the comment symbol when
  239
+    writing changes to a table. [#1167]
  240
+
  241
+- ``astropy.io.fits``
  242
+
  243
+  - Added a workaround for a bug in 64-bit OSX that could cause truncation when
  244
+    writing files greater than 2^32 bytes in size. [#839]
  245
+
  246
+- ``astropy.io.votable``
  247
+
  248
+  - Fixed incorrect reading of tables containing multiple ``<RESOURCE>``
  249
+    elements. [#1223]
  250
+
  251
+- ``astropy.table``
  252
+
  253
+  - Fixed a bug where ``Table.remove_column`` and ``Table.rename_column``
  254
+    could cause a maksed table to lose its masking. [#1120]
  255
+
  256
+  - Fixed bugs where subclasses of ``Table`` did not preserver their class in
  257
+    certain operations. [#1142]
  258
+
  259
+  - Fixed a bug where slicing a masked table did not preserve the mask. [#1187]
  260
+
  261
+- ``astropy.units``
  262
+
  263
+  - Fixed a bug where the ``.si`` and ``.cgs`` properties of dimensionless
  264
+    ``Quantity`` objects raised a ``ZeroDivisionError``. [#1150]
  265
+
  266
+  - Fixed a bug where multiple subsequent calls to the ``.decompose()`` method
  267
+    on array quantities applied a scale factor each time. [#1163]
  268
+
  269
+- Misc
  270
+
  271
+  - Fixed an installation crash that could occur sometimes on Debian/Ubuntu
  272
+    and other \*NIX systems where ``pkg_resources`` can be installed without
  273
+    installing ``setuptools``. [#1150]
  274
+
  275
+  - Updated the ``distribute_setup.py`` bootstrapper to use setuptools >= 0.7
  276
+    when installing on systems that don't already have an up to date version
  277
+    of distribute/setuptools. [#1180]
  278
+
  279
+  - Changed the ``version.py`` template so that Astropy affiliated packages can
  280
+    (and they should) use their own ``cython_version.py`` and
  281
+    ``utils._compiler`` modules where appropriate. This issue only pertains to
  282
+    affiliated package maintainers. [#1198]
  283
+
  284
+  - Fixed a corner case where the default config file generation could crash
  285
+    if building with matplotlib but *not* Sphinx installed in a virtualenv.
  286
+    [#1225]
  287
+
  288
+  - Fixed a crash that could occur in the logging module on systems that
  289
+    don't have a default preferred encoding (in particular this happened
  290
+    in some versions of PyCharm). [#1244]
  291
+
  292
+  - The Astropy log now supports passing non-string objects (and calling
  293
+    ``str()`` on them by default) to the logging methods, in line with Python's
  294
+    standard logging API. [#1267]
  295
+
  296
+  - Minor documentation fixes [#582, #696, #1154, #1194, #1212, #1213, #1246,
  297
+    #1252]
  298
+
  299
+Other Changes and Additions
  300
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
  301
+
  302
+- ``astropy.cosmology``
  303
+
  304
+  - Added a new ``Plank13`` object representing the Plank 2013 results. WMAP7
  305
+    remains the default cosmology so this should not affect any existing
  306
+    computations using the defaults. [#895]
  307
+
  308
+- ``astropy.units``
  309
+
  310
+  - Performance improvements in initialization of ``Quantity`` objects with
  311
+    a large number of elements. [#1231]
141 312
 
142 313
 
143 314
 0.2.3 (2013-05-30)
@@ -407,6 +578,11 @@ Bug Fixes
407 578
 
408 579
   - Fixed ``TypeError`` when calling ``WCS.to_header_string()``. [#822]
409 580
 
  581
+  - Added new method `WCS.all_world2pix` for converting from world coordinates
  582
+    to pixel space, including inversion of the astrometric distortion
  583
+    correction. [#1066, #1281]
  584
+
  585
+
410 586
 - Misc
411 587
 
412 588
   - Fixed a minor issue when installing with ``./setup.py develop`` on a fresh
27  astropy/__init__.py
@@ -17,6 +17,7 @@
17 17
         import __builtin__ as builtins
18 18
     builtins._ASTROPY_SETUP_ = False
19 19
     del version_info
  20
+    del builtins
20 21
 
21 22
 try:
22 23
     from .version import version as __version__
@@ -30,9 +31,6 @@
30 31
     __githash__ = ''
31 32
 
32 33
 
33  
-import logging
34  
-
35  
-
36 34
 # The location of the online documentation for astropy
37 35
 # This location will normally point to the current released version of astropy
38 36
 if 'dev' in __version__:
@@ -124,21 +122,15 @@ def test(package=None, test_path=None, args=None, plugins=None,
124 122
         parallel=parallel)
125 123
 
126 124
 
127  
-# Use the root logger as a dummy log before initilizing Astropy's logger
128  
-log = logging.getLogger()
129  
-
130 125
 # if we are *not* in setup mode, import the logger and possibly populate the
131 126
 # configuration file with the defaults
132  
-if not _ASTROPY_SETUP_:
133  
-    from .logger import _init_log
  127
+def _initialize_astropy():
134 128
     from . import config
135 129
 
136 130
     import os
137 131
     import sys
138 132
     from warnings import warn
139 133
 
140  
-    log = _init_log()
141  
-
142 134
     try:
143 135
         from .utils import _compiler
144 136
     except ImportError:
@@ -162,6 +154,17 @@ def test(package=None, test_path=None, args=None, plugins=None,
162 154
         wmsg = (e.args[0] + " Cannot install default profile. If you are "
163 155
                 "importing from source, this is expected.")
164 156
         warn(config.configuration.ConfigurationDefaultMissingWarning(wmsg))
165  
-        del e
166 157
 
167  
-    del _init_log, os, warn, config_dir  # clean up namespace
  158
+
  159
+import logging
  160
+
  161
+# Use the root logger as a dummy log before initilizing Astropy's logger
  162
+log = logging.getLogger()
  163
+
  164
+
  165
+if not _ASTROPY_SETUP_:
  166
+    from .logger import _init_log
  167
+
  168
+    log = _init_log()
  169
+
  170
+    _initialize_astropy()
7  astropy/constants/constant.py
@@ -57,7 +57,10 @@ def wrapper(self, *args, **kwargs):
57 57
 
58 58
         # The wrapper applies to so many of the __ methods that it's easier to
59 59
         # just exclude the ones it doesn't apply to
60  
-        exclude = set(['__init__', '__str__', '__repr__'])
  60
+        exclude = set(['__new__', '__array_finalize__', '__array_wrap__',
  61
+                       '__dir__', '__getattr__', '__init__', '__str__',
  62
+                       '__repr__', '__hash__', '__iter__', '__getitem__',
  63
+                       '__len__', '__nonzero__'])
61 64
         for attr, value in vars(Quantity).items():
62 65
             if (isinstance(value, types.FunctionType) and
63 66
                     attr.startswith('__') and attr.endswith('__') and
@@ -87,7 +90,7 @@ def __new__(cls, abbrev, name, value, unit, uncertainty, reference,
87 90
             warnings.warn('Constant {0!r} is already has a definition in the '
88 91
                           '{1!r} system'.format(name, system))
89 92
 
90  
-        inst = super(Constant, cls).__new__(cls)
  93
+        inst = super(Constant, cls).__new__(cls, value)
91 94
 
92 95
         for c in instances.values():
93 96
             if system is not None and not hasattr(c.__class__, system):
4  astropy/constants/si.py
@@ -30,6 +30,10 @@
30 30
 G = Constant('G', "Gravitational constant", 6.67384e-11, 'm3 / (kg s2)',
31 31
              0.00080e-11, 'CODATA 2010', system='si')
32 32
 
  33
+# Standard acceleration of gravity
  34
+g0 = Constant('g0', "Standard acceleration of gravity", 9.80665, 'm / s2', 0.0,
  35
+              'CODATA 2010', system='si')
  36
+
33 37
 # Proton mass
34 38
 m_p = Constant('m_p', "Proton mass", 1.672621777e-27, 'kg', 0.000000074e-27,
35 39
                'CODATA 2010', system='si')
19  astropy/constants/tests/test_constant.py
@@ -66,6 +66,25 @@ def test_e():
66 66
     assert e.esu * E == Q(e.esu.value * E.value, 'Fr V/m')
67 67
 
68 68
 
  69
+def test_g0():
  70
+    """Tests for #1263 demonstrating how g0 constant should behave."""
  71
+    from .. import g0
  72
+
  73
+    # g0 is an exactly defined constant, so it shouldn't be changing
  74
+    assert g0.value == 9.80665  # default is S.I.
  75
+    assert g0.si.value == 9.80665
  76
+    assert g0.cgs.value == 9.80665e2
  77
+
  78
+    # make sure it has the necessary attributes and they're not blank
  79
+    assert g0.uncertainty == 0  # g0 is a *defined* quantity
  80
+    assert g0.name
  81
+    assert g0.reference
  82
+    assert g0.unit
  83
+
  84
+    # Check that its unit have the correct physical type
  85
+    assert g0.unit.physical_type == 'acceleration'
  86
+
  87
+
69 88
 def test_unit():
70 89
 
71 90
     from ... import units as u
2  astropy/coordinates/angle_lextab.py
@@ -4,6 +4,6 @@
4 4
 _lexreflags   = 0
5 5
 _lexliterals  = ''
6 6
 _lexstateinfo = {'INITIAL': 'inclusive'}
7  
-_lexstatere   = {'INITIAL': [(u'(?P<t_UFLOAT>((\\d+\\.\\d*)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-])|(?P<t_SIMPLE_UNIT>(arcminute)|(rad)|(arcsec)|(uas)|(arcsecond)|(mas)|(arcmin)|(radian))|(?P<t_MINUTE>m(in(ute(s)?)?)?|\u2032|\\\')|(?P<t_SECOND>s(ec(ond(s)?)?)?|\u2033|\\")|(?P<t_DEGREE>d(eg(ree(s)?)?)?|\xb0)|(?P<t_HOUR>hour(s)?|h(r)?|\u02b0)|(?P<t_COLON>:)', [None, (u't_UFLOAT', 'UFLOAT'), None, None, None, None, (u't_UINT', 'UINT'), (u't_SIGN', 'SIGN'), (None, 'SIMPLE_UNIT'), None, None, None, None, None, None, None, None, (None, 'MINUTE'), None, None, None, (None, 'SECOND'), None, None, None, (None, 'DEGREE'), None, None, None, (None, 'HOUR'), None, None, (None, 'COLON')])]}
  7
+_lexstatere   = {'INITIAL': [(u'(?P<t_UFLOAT>((\\d+\\.\\d*)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-])|(?P<t_SIMPLE_UNIT>(?:hectoradian)|(?:petaradian)|(?:decaarcminute)|(?:hrad)|(?:zeptoradian)|(?:arcsec)|(?:aarcmin)|(?:Marcmin)|(?:Parcsec)|(?:kiloarcsecond)|(?:exaarcsecond)|(?:deciradian)|(?:yoctoarcminute)|(?:prad)|(?:Parcmin)|(?:yottaradian)|(?:marcmin)|(?:aarcsec)|(?:milliarcsecond)|(?:milliradian)|(?:parcsec)|(?:arad)|(?:uarcmin)|(?:parcmin)|(?:radian)|(?:dekaarcsecond)|(?:teraarcsecond)|(?:Tarcsec)|(?:attoarcsecond)|(?:yarcsec)|(?:krad)|(?:Zarcmin)|(?:Earcmin)|(?:exaarcminute)|(?:farcmin)|(?:Prad)|(?:dekaarcminute)|(?:Earcsec)|(?:Yarcsec)|(?:Garcmin)|(?:daarcmin)|(?:kiloradian)|(?:nanoarcminute)|(?:kiloarcminute)|(?:megaradian)|(?:zettaarcsecond)|(?:Zarcsec)|(?:carcsec)|(?:femtoarcsecond)|(?:Marcsec)|(?:arcminute)|(?:yarcmin)|(?:yoctoarcsecond)|(?:arcsecond)|(?:decaarcsecond)|(?:zeptoarcminute)|(?:attoradian)|(?:Grad)|(?:microarcminute)|(?:nrad)|(?:marcsec)|(?:picoarcminute)|(?:teraradian)|(?:narcsec)|(?:milliarcminute)|(?:zettaradian)|(?:darad)|(?:microradian)|(?:centiradian)|(?:gigaarcsecond)|(?:decaradian)|(?:exaradian)|(?:centiarcminute)|(?:cy)|(?:femtoradian)|(?:mrad)|(?:femtoarcminute)|(?:yottaarcsecond)|(?:centiarcsecond)|(?:gigaradian)|(?:zettaarcminute)|(?:gigaarcminute)|(?:yoctoradian)|(?:crad)|(?:picoradian)|(?:zrad)|(?:dekaradian)|(?:narcmin)|(?:farcsec)|(?:Erad)|(?:hectoarcminute)|(?:urad)|(?:nanoarcsecond)|(?:microarcsecond)|(?:hectoarcsecond)|(?:petaarcsecond)|(?:deciarcsecond)|(?:nanoradian)|(?:Mrad)|(?:carcmin)|(?:yottaarcminute)|(?:rad)|(?:uas)|(?:harcmin)|(?:megaarcsecond)|(?:yrad)|(?:Zrad)|(?:Yarcmin)|(?:zeptoarcsecond)|(?:zarcmin)|(?:darcmin)|(?:karcmin)|(?:attoarcminute)|(?:daarcsec)|(?:Tarcmin)|(?:Trad)|(?:mas)|(?:frad)|(?:petaarcminute)|(?:teraarcminute)|(?:harcsec)|(?:uarcsec)|(?:drad)|(?:arcmin)|(?:Garcsec)|(?:darcsec)|(?:zarcsec)|(?:cycle)|(?:karcsec)|(?:megaarcminute)|(?:deciarcminute)|(?:picoarcsecond)|(?:Yrad))|(?P<t_MINUTE>m(in(ute(s)?)?)?|\u2032|\\\'|\u1d50)|(?P<t_SECOND>s(ec(ond(s)?)?)?|\u2033|\\"|\u02e2)|(?P<t_DEGREE>d(eg(ree(s)?)?)?|\xb0)|(?P<t_HOUR>hour(s)?|h(r)?|\u02b0)|(?P<t_COLON>:)', [None, (u't_UFLOAT', 'UFLOAT'), None, None, None, None, (u't_UINT', 'UINT'), (u't_SIGN', 'SIGN'), (u't_SIMPLE_UNIT', 'SIMPLE_UNIT'), (None, 'MINUTE'), None, None, None, (None, 'SECOND'), None, None, None, (None, 'DEGREE'), None, None, None, (None, 'HOUR'), None, None, (None, 'COLON')])]}
8 8
 _lexstateignore = {'INITIAL': u' '}
9 9
 _lexstateerrorf = {'INITIAL': 't_error'}
62  astropy/coordinates/angle_parsetab.py
@@ -5,9 +5,9 @@
5 5
 
6 6
 _lr_method = 'LALR'
7 7
 
8  
-_lr_signature = '\xf9\xf2\xc0%N\xfc\x14x\x1d\xd1\xa0\x02\x1d\xabJf'
9  
-    
10  
-_lr_action_items = {u'DEGREE':([1,6,8,10,11,17,21,22,23,24,30,],[-13,13,-12,15,-14,-10,-11,-7,-6,-8,-9,]),u'HOUR':([1,6,8,10,11,17,21,22,23,24,30,],[-13,14,-12,16,-14,-10,-11,-7,-6,-8,-9,]),u'SIGN':([0,],[4,]),u'SECOND':([22,23,28,29,],[-7,-6,31,32,]),u'COLON':([10,24,],[18,27,]),u'SIMPLE_UNIT':([1,6,8,10,11,17,21,22,23,24,30,],[-13,12,-12,-15,-14,-10,-11,-7,-6,-8,-9,]),u'UINT':([0,4,5,10,15,16,17,18,25,26,27,],[-5,-4,10,17,19,20,22,24,22,22,22,]),'$end':([1,2,3,6,7,8,9,10,11,12,13,14,17,19,20,21,22,23,24,25,26,28,29,30,31,32,],[-13,0,-3,-26,-2,-12,-1,-15,-14,-27,-25,-20,-10,-21,-16,-11,-7,-6,-8,-22,-17,-23,-18,-9,-24,-19,]),u'MINUTE':([19,20,],[25,26,]),u'UFLOAT':([0,4,5,17,25,26,27,],[-5,-4,11,23,23,23,23,]),}
  8
+_lr_signature = '\xd1\xb8\xf69G\t\xd0\xd8*\x05\xa4\x13\x8b\x945\\'
  9
+
  10
+_lr_action_items = {u'DEGREE':([1,6,8,10,11,17,21,22,23,24,30,],[-13,13,-12,15,-14,-10,-11,-7,-6,-8,-9,]),u'HOUR':([1,6,8,10,11,17,21,22,23,24,30,],[-13,14,-12,16,-14,-10,-11,-7,-6,-8,-9,]),u'SIGN':([0,],[4,]),u'SECOND':([22,23,28,29,],[-7,-6,31,32,]),u'COLON':([10,24,],[18,27,]),u'SIMPLE_UNIT':([1,6,8,10,11,17,21,22,23,24,30,],[-13,12,-12,-15,-14,-10,-11,-7,-6,-8,-9,]),u'UINT':([0,4,5,10,15,16,17,18,25,26,27,],[-5,-4,10,17,19,20,22,24,22,22,22,]),'$end':([1,2,3,6,7,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,26,28,29,30,31,32,],[-13,0,-3,-28,-2,-12,-1,-15,-14,-29,-27,-21,-22,-16,-10,-23,-17,-11,-7,-6,-8,-24,-18,-25,-19,-9,-26,-20,]),u'MINUTE':([19,20,],[25,26,]),u'UFLOAT':([0,4,5,17,25,26,27,],[-5,-4,11,23,23,23,23,]),}
11 11
 
12 12
 _lr_action = { }
13 13
 for _k, _v in _lr_action_items.items():
@@ -26,31 +26,33 @@
26 26
 del _lr_goto_items
27 27
 _lr_productions = [
28 28
   ("S' -> angle","S'",1,None,None,None),
29  
-  (u'angle -> hms',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',107),
30  
-  (u'angle -> dms',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',108),
31  
-  (u'angle -> simple',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',109),
32  
-  (u'sign -> SIGN',u'sign',1,'p_sign','astropy/coordinates/angle_utilities.py',115),
33  
-  (u'sign -> <empty>',u'sign',0,'p_sign','astropy/coordinates/angle_utilities.py',116),
34  
-  (u'ufloat -> UFLOAT',u'ufloat',1,'p_ufloat','astropy/coordinates/angle_utilities.py',125),
35  
-  (u'ufloat -> UINT',u'ufloat',1,'p_ufloat','astropy/coordinates/angle_utilities.py',126),
36  
-  (u'colon -> sign UINT COLON UINT',u'colon',4,'p_colon','astropy/coordinates/angle_utilities.py',132),
37  
-  (u'colon -> sign UINT COLON UINT COLON ufloat',u'colon',6,'p_colon','astropy/coordinates/angle_utilities.py',133),
38  
-  (u'spaced -> sign UINT UINT',u'spaced',3,'p_spaced','astropy/coordinates/angle_utilities.py',142),
39  
-  (u'spaced -> sign UINT UINT ufloat',u'spaced',4,'p_spaced','astropy/coordinates/angle_utilities.py',143),
40  
-  (u'generic -> colon',u'generic',1,'p_generic','astropy/coordinates/angle_utilities.py',152),
41  
-  (u'generic -> spaced',u'generic',1,'p_generic','astropy/coordinates/angle_utilities.py',153),
42  
-  (u'generic -> sign UFLOAT',u'generic',2,'p_generic','astropy/coordinates/angle_utilities.py',154),
43  
-  (u'generic -> sign UINT',u'generic',2,'p_generic','astropy/coordinates/angle_utilities.py',155),
44  
-  (u'hms -> sign UINT HOUR UINT',u'hms',4,'p_hms','astropy/coordinates/angle_utilities.py',164),
45  
-  (u'hms -> sign UINT HOUR UINT MINUTE',u'hms',5,'p_hms','astropy/coordinates/angle_utilities.py',165),
46  
-  (u'hms -> sign UINT HOUR UINT MINUTE ufloat',u'hms',6,'p_hms','astropy/coordinates/angle_utilities.py',166),
47  
-  (u'hms -> sign UINT HOUR UINT MINUTE ufloat SECOND',u'hms',7,'p_hms','astropy/coordinates/angle_utilities.py',167),
48  
-  (u'hms -> generic HOUR',u'hms',2,'p_hms','astropy/coordinates/angle_utilities.py',168),
49  
-  (u'dms -> sign UINT DEGREE UINT',u'dms',4,'p_dms','astropy/coordinates/angle_utilities.py',179),
50  
-  (u'dms -> sign UINT DEGREE UINT MINUTE',u'dms',5,'p_dms','astropy/coordinates/angle_utilities.py',180),
51  
-  (u'dms -> sign UINT DEGREE UINT MINUTE ufloat',u'dms',6,'p_dms','astropy/coordinates/angle_utilities.py',181),
52  
-  (u'dms -> sign UINT DEGREE UINT MINUTE ufloat SECOND',u'dms',7,'p_dms','astropy/coordinates/angle_utilities.py',182),
53  
-  (u'dms -> generic DEGREE',u'dms',2,'p_dms','astropy/coordinates/angle_utilities.py',183),
54  
-  (u'simple -> generic',u'simple',1,'p_simple','astropy/coordinates/angle_utilities.py',194),
55  
-  (u'simple -> generic SIMPLE_UNIT',u'simple',2,'p_simple','astropy/coordinates/angle_utilities.py',195),
  29
+  (u'angle -> hms',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',112),
  30
+  (u'angle -> dms',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',113),
  31
+  (u'angle -> simple',u'angle',1,'p_angle','astropy/coordinates/angle_utilities.py',114),
  32
+  (u'sign -> SIGN',u'sign',1,'p_sign','astropy/coordinates/angle_utilities.py',120),
  33
+  (u'sign -> <empty>',u'sign',0,'p_sign','astropy/coordinates/angle_utilities.py',121),
  34
+  (u'ufloat -> UFLOAT',u'ufloat',1,'p_ufloat','astropy/coordinates/angle_utilities.py',130),
  35
+  (u'ufloat -> UINT',u'ufloat',1,'p_ufloat','astropy/coordinates/angle_utilities.py',131),
  36
+  (u'colon -> sign UINT COLON UINT',u'colon',4,'p_colon','astropy/coordinates/angle_utilities.py',137),
  37
+  (u'colon -> sign UINT COLON UINT COLON ufloat',u'colon',6,'p_colon','astropy/coordinates/angle_utilities.py',138),
  38
+  (u'spaced -> sign UINT UINT',u'spaced',3,'p_spaced','astropy/coordinates/angle_utilities.py',147),
  39
+  (u'spaced -> sign UINT UINT ufloat',u'spaced',4,'p_spaced','astropy/coordinates/angle_utilities.py',148),
  40
+  (u'generic -> colon',u'generic',1,'p_generic','astropy/coordinates/angle_utilities.py',157),
  41
+  (u'generic -> spaced',u'generic',1,'p_generic','astropy/coordinates/angle_utilities.py',158),
  42
+  (u'generic -> sign UFLOAT',u'generic',2,'p_generic','astropy/coordinates/angle_utilities.py',159),
  43
+  (u'generic -> sign UINT',u'generic',2,'p_generic','astropy/coordinates/angle_utilities.py',160),
  44
+  (u'hms -> sign UINT HOUR',u'hms',3,'p_hms','astropy/coordinates/angle_utilities.py',169),
  45
+  (u'hms -> sign UINT HOUR UINT',u'hms',4,'p_hms','astropy/coordinates/angle_utilities.py',170),
  46
+  (u'hms -> sign UINT HOUR UINT MINUTE',u'hms',5,'p_hms','astropy/coordinates/angle_utilities.py',171),
  47
+  (u'hms -> sign UINT HOUR UINT MINUTE ufloat',u'hms',6,'p_hms','astropy/coordinates/angle_utilities.py',172),
  48
+  (u'hms -> sign UINT HOUR UINT MINUTE ufloat SECOND',u'hms',7,'p_hms','astropy/coordinates/angle_utilities.py',173),
  49
+  (u'hms -> generic HOUR',u'hms',2,'p_hms','astropy/coordinates/angle_utilities.py',174),
  50
+  (u'dms -> sign UINT DEGREE',u'dms',3,'p_dms','astropy/coordinates/angle_utilities.py',185),
  51
+  (u'dms -> sign UINT DEGREE UINT',u'dms',4,'p_dms','astropy/coordinates/angle_utilities.py',186),
  52
+  (u'dms -> sign UINT DEGREE UINT MINUTE',u'dms',5,'p_dms','astropy/coordinates/angle_utilities.py',187),
  53
+  (u'dms -> sign UINT DEGREE UINT MINUTE ufloat',u'dms',6,'p_dms','astropy/coordinates/angle_utilities.py',188),
  54
+  (u'dms -> sign UINT DEGREE UINT MINUTE ufloat SECOND',u'dms',7,'p_dms','astropy/coordinates/angle_utilities.py',189),
  55
+  (u'dms -> generic DEGREE',u'dms',2,'p_dms','astropy/coordinates/angle_utilities.py',190),
  56
+  (u'simple -> generic',u'simple',1,'p_simple','astropy/coordinates/angle_utilities.py',201),
  57
+  (u'simple -> generic SIMPLE_UNIT',u'simple',2,'p_simple','astropy/coordinates/angle_utilities.py',202),
56 58
 ]
172  astropy/coordinates/angle_utilities.py
@@ -12,6 +12,8 @@
12 12
 import os
13 13
 from warnings import warn
14 14
 
  15
+import numpy as np
  16
+
15 17
 from .errors import *
16 18
 from ..utils import format_exception
17 19
 from .. import units as u
@@ -36,7 +38,8 @@ def __init__(self):
36 38
 
37 39
     @classmethod
38 40
     def _get_simple_unit_names(cls):
39  
-        simple_units = set(u.radian.find_equivalent_units())
  41
+        simple_units = set(
  42
+            u.radian.find_equivalent_units(include_prefix_units=True))
40 43
         simple_units.remove(u.deg)
41 44
         simple_units.remove(u.hourangle)
42 45
         simple_unit_names = set()
@@ -78,13 +81,17 @@ def t_SIGN(t):
78 81
             t.value = float(t.value + '1')
79 82
             return t
80 83
 
  84
+        def t_SIMPLE_UNIT(t):
  85
+            t.value = u.Unit(t.value)
  86
+            return t
  87
+        t_SIMPLE_UNIT.__doc__ = '|'.join(
  88
+            '(?:{0})'.format(x) for x in cls._get_simple_unit_names())
  89
+
81 90
         t_COLON = ':'
82 91
         t_DEGREE = r'd(eg(ree(s)?)?)?|°'
83 92
         t_HOUR = r'hour(s)?|h(r)?|ʰ'
84  
-        t_MINUTE = r'm(in(ute(s)?)?)?|′|\''
85  
-        t_SECOND = r's(ec(ond(s)?)?)?|″|\"'
86  
-        t_SIMPLE_UNIT = '|'.join(
87  
-            '({0})'.format(x) for x in cls._get_simple_unit_names())
  93
+        t_MINUTE = r'm(in(ute(s)?)?)?|′|\'|ᵐ'
  94
+        t_SECOND = r's(ec(ond(s)?)?)?|″|\"|ˢ'
88 95
 
89 96
         # A string containing ignored characters (spaces)
90 97
         t_ignore = ' '
@@ -168,8 +175,10 @@ def p_hms(p):
168 175
                 | sign UINT HOUR UINT MINUTE ufloat SECOND
169 176
                 | generic HOUR
170 177
             '''
171  
-            if len(p) in (3, 4):
  178
+            if len(p) == 3:
172 179
                 p[0] = (p[1], u.hourangle)
  180
+            elif len(p) == 4:
  181
+                p[0] = (p[1] * p[2], u.hourangle)
173 182
             elif len(p) in (5, 6):
174 183
                 p[0] = ((p[1] * p[2], p[4], 0.0), u.hourangle)
175 184
             elif len(p) in (7, 8):
@@ -184,8 +193,10 @@ def p_dms(p):
184 193
                 | sign UINT DEGREE UINT MINUTE ufloat SECOND
185 194
                 | generic DEGREE
186 195
             '''
187  
-            if len(p) in (3, 4):
  196
+            if len(p) == 3:
188 197
                 p[0] = (p[1], u.degree)
  198
+            elif len(p) == 4:
  199
+                p[0] = (p[1] * p[2], u.degree)
189 200
             elif len(p) in (5, 6):
190 201
                 p[0] = ((p[1] * p[2], p[4], 0.0), u.degree)
191 202
             elif len(p) in (7, 8):
@@ -230,9 +241,7 @@ def parse(self, angle, unit, debug=False):
230 241
             unit = u.Unit(unit)
231 242
             if (found_unit is not None and
232 243
                 found_unit is not unit):
233  
-                raise u.UnitsException(
234  
-                    "Unit in string ({0}) does not match requested unit "
235  
-                    "({1})".format(found_unit, unit))
  244
+                found_angle = found_unit.to(unit, found_angle)
236 245
         else:
237 246
             if found_unit is None:
238 247
                 raise u.UnitsException("No unit specified")
@@ -246,22 +255,22 @@ def _check_hour_range(hrs):
246 255
     """
247 256
     Checks that the given value is in the range (-24, 24).
248 257
     """
249  
-    if math.fabs(hrs) == 24.:
  258
+    if np.any(np.abs(hrs) == 24.):
250 259
         warn(IllegalHourWarning(hrs, 'Treating as 24 hr'))
251  
-    elif not -24. < hrs < 24.:
  260
+    elif np.any(hrs < -24.) or np.any(hrs > 24.):
252 261
         raise IllegalHourError(hrs)
253 262
 
254 263
 
255  
-def _check_minute_range(min):
  264
+def _check_minute_range(m):
256 265
     """
257 266
     Checks that the given value is in the range [0,60].  If the value
258 267
     is equal to 60, then a warning is raised.
259 268
     """
260  
-    if min == 60.:
261  
-        warn(IllegalMinuteWarning(min, 'Treating as 0 min, +1 hr/deg'))
262  
-    elif not 0. <= min < 60.:
  269
+    if np.any(m == 60.):
  270
+        warn(IllegalMinuteWarning(m, 'Treating as 0 min, +1 hr/deg'))
  271
+    elif np.any(m < 0.) or np.any(m > 60.):
263 272
         # "Error: minutes not in range [0,60) ({0}).".format(min))
264  
-        raise IllegalMinuteError(min)
  273
+        raise IllegalMinuteError(m)
265 274
 
266 275
 
267 276
 def _check_second_range(sec):
@@ -269,9 +278,9 @@ def _check_second_range(sec):
269 278
     Checks that the given value is in the range [0,60].  If the value
270 279
     is equal to 60, then a warning is raised.
271 280
     """
272  
-    if sec == 60.:
  281
+    if np.any(sec == 60.):
273 282
         warn(IllegalSecondWarning(sec, 'Treating as 0 sec, +1 min'))
274  
-    elif not 0. <= sec < 60.:
  283
+    elif np.any(sec < 0.) or np.any(sec > 60.):
275 284
         # "Error: seconds not in range [0,60) ({0}).".format(sec))
276 285
         raise IllegalSecondError(sec)
277 286
 
@@ -326,16 +335,16 @@ def degrees_to_dms(d):
326 335
     Convert a floating-point degree value into a ``(degree, arcminute,
327 336
     arcsecond)`` tuple.
328 337
     """
329  
-    sign = math.copysign(1.0, d)
  338
+    sign = np.copysign(1.0, d)
330 339
 
331  
-    (df, d) = math.modf(abs(d))  # (degree fraction, degree)
332  
-    (mf, m) = math.modf(df * 60.)  # (minute fraction, minute)
  340
+    (df, d) = np.modf(np.abs(d))  # (degree fraction, degree)
  341
+    (mf, m) = np.modf(df * 60.)  # (minute fraction, minute)
333 342
     s = mf * 60.
334 343
 
335 344
     _check_minute_range(m)
336 345
     _check_second_range(s)
337 346
 
338  
-    return (float(sign * d), int(sign * m), sign * s)
  347
+    return np.floor(sign * d), np.floor(m), s
339 348
 
340 349
 
341 350
 def dms_to_degrees(d, m, s):
@@ -347,15 +356,15 @@ def dms_to_degrees(d, m, s):
347 356
     _check_second_range(s)
348 357
 
349 358
     # determine sign
350  
-    sign = math.copysign(1.0, d)
  359
+    sign = np.copysign(1.0, d)
351 360
 
352 361
     # TODO: This will fail if d or m have values after the decimal
353 362
     # place
354 363
 
355 364
     try:
356  
-        d = int(abs(d))
357  
-        m = int(abs(m))
358  
-        s = float(abs(s))
  365
+        d = np.floor(np.abs(np.asarray(d)))
  366
+        m = np.floor(np.abs(np.asarray(m)))
  367
+        s = np.abs(s)
359 368
     except ValueError:
360 369
         raise ValueError(format_exception(
361 370
             "{func}: dms values ({1[0]},{2[1]},{3[2]}) could not be "
@@ -372,15 +381,15 @@ def hms_to_hours(h, m, s):
372 381
     check_hms_ranges(h, m, s)
373 382
 
374 383
     # determine sign
375  
-    sign = math.copysign(1.0, h)
  384
+    sign = np.copysign(1.0, h)
376 385
 
377 386
     # TODO: This will fail if d or m have values after the decimal
378 387
     # place
379 388
 
380 389
     try:
381  
-        h = int(abs(h))
382  
-        m = int(abs(m))
383  
-        s = float(abs(s))
  390
+        h = np.floor(np.abs(h))
  391
+        m = np.floor(np.abs(m))
  392
+        s = np.abs(s)
384 393
     except ValueError:
385 394
         raise ValueError(format_exception(
386 395
             "{func}: HMS values ({1[0]},{2[1]},{3[2]}) could not be "
@@ -436,15 +445,15 @@ def hours_to_hms(h):
436 445
     second)`` tuple.
437 446
     """
438 447
 
439  
-    sign = math.copysign(1.0, h)
  448
+    sign = np.copysign(1.0, h)
440 449
 
441  
-    (hf, h) = math.modf(abs(h))  # (degree fraction, degree)
442  
-    (mf, m) = math.modf(hf * 60.0)  # (minute fraction, minute)
  450
+    (hf, h) = np.modf(np.abs(h))  # (degree fraction, degree)
  451
+    (mf, m) = np.modf(hf * 60.0)  # (minute fraction, minute)
443 452
     s = mf * 60.0
444 453
 
445 454
     check_hms_ranges(h, m, s)  # throws exception if out of range
446 455
 
447  
-    return (float(sign * h), int(sign * m), sign * s)
  456
+    return (np.floor(sign * h), np.floor(m), s)
448 457
 
449 458
 
450 459
 def radians_to_degrees(r):
@@ -480,16 +489,17 @@ def radians_to_dms(r):
480 489
     return degrees_to_dms(degrees)
481 490
 
482 491
 
483  
-def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's')):
  492
+def sexagesimal_to_string(values, precision=5, pad=False, sep=(':',),
  493
+                          fields=3):
484 494
     """
485  
-    Takes a decimal hour value and returns a string formatted as hms with
486  
-    separator specified by the 'sep' parameter.
  495
+    Given an already separated tuple of sexagesimal values, returns
  496
+    a string.
487 497
 
488  
-    TODO: More detailed description here!
  498
+    See `hours_to_string` and `degrees_to_string` for a higher-level
  499
+    interface to this functionality.
489 500
     """
490  
-
491 501
     if pad:
492  
-        if h < 0:
  502
+        if values[0] < 0:
493 503
             pad = 3
494 504
         else:
495 505
             pad = 2
@@ -497,8 +507,6 @@ def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's')):
497 507
         pad = 0
498 508
 
499 509
     if not isinstance(sep, tuple):
500  
-        # Note: This will convert 'hms' to ('h', 'm', 's'); a potentially nice
501  
-        # shortcut
502 510
         sep = tuple(sep)
503 511
 
504 512
     if len(sep) == 1:
@@ -509,43 +517,63 @@ def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's')):
509 517
         raise ValueError(
510 518
             "Invalid separator specification for converting angle to string.")
511 519
 
512  
-    literal = ('{0:0{pad}.0f}{sep[0]}{1:02d}{sep[1]}{2:0{width}.{precision}f}'
513  
-               '{sep[2]}')
  520
+    if fields < 1 or fields > 3:
  521
+        raise ValueError(
  522
+            "fields must be 1, 2, or 3")
  523
+
  524
+    # Simplify the expression based on the requested precision.  For
  525
+    # example, if the seconds will round up to 60, we should convert
  526
+    # it to 0 and carry upwards.  If the field is hidden (by the
  527
+    # fields kwarg) we round up around the middle, 30.0.
  528
+    values = list(values)
  529
+    if fields == 3 and values[2] >= 60.0 - (10.0 ** -precision):
  530
+        values[2] = 0.0
  531
+        values[1] += 1.0
  532
+    elif fields < 3 and values[2] >= 30.0:
  533
+        values[1] += 1.0
  534
+
  535
+    if fields >= 2 and int(values[1]) >= 60.0:
  536
+        values[1] = 0.0
  537
+        values[0] += 1.0
  538
+    elif fields < 2 and int(values[1]) >= 30.0:
  539
+        values[0] += 1.0
  540
+
  541
+    literal = []
  542
+    literal.append('{0:0{pad}.0f}{sep[0]}')
  543
+    if fields >= 2:
  544
+        literal.append('{1:02d}{sep[1]}')
  545
+    if fields == 3:
  546
+        literal.append('{2:0{width}.{precision}f}{sep[2]}')
  547
+    literal = ''.join(literal)
  548
+    return literal.format(values[0], int(abs(values[1])), abs(values[2]),
  549
+                          sep=sep, pad=pad,
  550
+                          width=(precision + 3 if precision > 0 else 2),
  551
+                          precision=precision)
  552
+
  553
+
  554
+def hours_to_string(h, precision=5, pad=False, sep=('h', 'm', 's'),
  555
+                    fields=3):
  556
+    """
  557
+    Takes a decimal hour value and returns a string formatted as hms with
  558
+    separator specified by the 'sep' parameter.
  559
+
  560
+    `h` must be a scalar.
  561
+    """
514 562
     h, m, s = hours_to_hms(h)
515  
-    return literal.format(h, abs(m), abs(s), sep=sep, pad=pad,
516  
-                          width=(precision + 3), precision=precision)
  563
+    return sexagesimal_to_string((h, m, s), precision=precision, pad=pad,
  564
+                                 sep=sep, fields=fields)
517 565
 
518 566
 
519  
-def degrees_to_string(d, precision=5, pad=False, sep=':'):
  567
+def degrees_to_string(d, precision=5, pad=False, sep=':', fields=3):
520 568
     """
521 569
     Takes a decimal hour value and returns a string formatted as dms with
522 570
     separator specified by the 'sep' parameter.
523  
-    """
524  
-
525  
-    if pad:
526  
-        if d < 0:
527  
-            pad = 3
528  
-        else:
529  
-            pad = 2
530  
-    else:
531  
-        pad = 0
532  
-
533  
-    if not isinstance(sep, tuple):
534  
-        sep = tuple(sep)
535  
-
536  
-    if len(sep) == 1:
537  
-        sep = sep + (sep[0], '')
538  
-    elif len(sep) == 2:
539  
-        sep = sep + ('',)
540  
-    elif len(sep) != 3:
541  
-        raise ValueError(
542  
-            "Invalid separator specification for converting angle to string.")
543 571
 
544  
-    literal = ('{0:0{pad}.0f}{sep[0]}{1:02d}{sep[1]}{2:0{width}.{precision}f}'
545  
-               '{sep[2]}')
  572
+    `d` must be a scalar.
  573
+    """
546 574
     d, m, s = degrees_to_dms(d)
547  
-    return literal.format(d, abs(m), abs(s), sep=sep, pad=pad,
548  
-                          width=(precision + 3), precision=precision)
  575
+    return sexagesimal_to_string((d, m, s), precision=precision, pad=pad,
  576
+                                 sep=sep, fields=fields)
549 577
 
550 578
 
551 579
 #<----------Spherical angular distances------------->
875  astropy/coordinates/angles.py
@@ -14,77 +14,142 @@
14 14
 from . import angle_utilities as util
15 15
 from .errors import *
16 16
 from .. import units as u
17  
-from ..utils.compat.odict import OrderedDict
  17
+from ..utils import deprecated
  18
+
18 19
 
19 20
 __all__ = ['Angle', 'RA', 'Dec', 'AngularSeparation']
20 21
 
  22
+
21 23
 TWOPI = math.pi * 2.0  # no need to calculate this all the time
22 24
 
23 25
 
24  
-class Angle(object):
25  
-    """ An angle.
  26
+class Angle(u.Quantity):
  27
+    """
  28
+    An angle.
26 29
 
27  
-    An angle can be specified either as a float, tuple (see below),
28  
-    or string.  If A string, it must be in one of the following formats:
  30
+    An angle can be specified either as an array, scalar, tuple (see
  31
+    below), string, `~astropy.units.Quantity` or another
  32
+    `~astropy.coordinates.Angle`.
29 33
 
30  
-    * '1:2:3.4'
31  
-    * '1 2 3.4'
32  
-    * '1h2m3.4s'
33  
-    * '1d2m3.4s'
  34
+    If a string, it must be in one of the following formats:
  35
+
  36
+        * ``'1:2:30.43 degrees'``
  37
+        * ``'1 2 0 hours'``
  38
+        * ``'1°2′3″'``
  39
+        * ``'1d2m3.4s'``
  40
+        * ``'-1h2m3s'``
34 41
 
35 42
     Parameters
36 43
     ----------
37  
-    angle : float, int, str, tuple
38  
-        The angle value. If a tuple, will be interpreted as (h, m s) or
39  
-        (d, m, s) depending on `unit`. If a string, it will be interpreted
40  
-        following the rules described above.
41  
-    unit : `~astropy.units.UnitBase`, str
42  
-        The unit of the value specified for the angle.  This may be any
43  
-        string that `~astropy.units.Unit` understands, but it is better to
44  
-        give an actual unit object.  Must be one of `~astropy.units.degree`,
45  
-        `~astropy.units.radian`, or `~astropy.units.hour`.
46  
-    bounds : tuple
47  
-        A tuple indicating the upper and lower value that the new angle object may
48  
-        have.
  44
+    angle : array, scalar, Quantity, Angle
  45
+        The angle value. If a tuple, will be interpreted as ``(h, m
  46
+        s)`` or ``(d, m, s)`` depending on `unit`. If a string, it
  47
+        will be interpreted following the rules described above.
  48
+
  49
+        If `angle` is a sequence or array of strings, the resulting
  50
+        values will be in the given `unit`, or if None is provided,
  51
+        the unit will be taken from the first given value.
  52
+
  53
+    unit : `~astropy.units.UnitBase`, str, optional
  54
+        The unit of the value specified for the angle.  This may be
  55
+        any string that `~astropy.units.Unit` understands, but it is
  56
+        better to give an actual unit object.  Must be an angular
  57
+        unit.
  58
+
  59
+    bounds : 2-sequence, optional
  60
+        A length-2 sequence indicating the upper and lower value that
  61
+        the new angle object may have.  Each value may be in any of
  62
+        the forms accepted by the `angle` argument.  If no units are
  63
+        specified, (that is the value is a float, tuple, or string
  64
+        without an explicit unit), the unit is taken from the `unit`
  65
+        argument.  Pass `None` to perform no bounds checking.  By
  66
+        default the bounds are `(-360, 360)` degrees.
  67
+
  68
+    dtype : ~numpy.dtype, optional
  69
+        See `~astropy.units.Quantity`.
  70
+
  71
+    equivalencies : list of equivalence pairs, optional
  72
+        See `~astropy.units.Quantity`.
49 73
 
50 74
     Raises
51 75
     ------
52 76
     `~astropy.units.core.UnitsException`
53  
-        If a unit is not provided or it is not hour, radian, or degree.
54  
-
  77
+        If a unit is not provided or it is not an angular unit.
55 78
     """
  79
+    def __new__(cls, angle, unit=None, bounds=(), dtype=None,
  80
+                equivalencies=[]):
  81
+        unit = cls._convert_unit_to_angle_unit(unit)
  82
+        if (unit is not None and
  83
+            not unit.is_equivalent(u.radian, equivalencies)):
  84
+            raise u.UnitsException(
  85
+                "Given unit {0} is not convertible to an angle".format(
  86
+                    unit))
  87
+
  88
+        if isinstance(angle, u.Quantity):
  89
+            # This includes Angle subclasses as well
  90
+            if unit is not None:
  91
+                angle = angle.to(unit).value
  92
+            else:
  93
+                unit = angle.unit
  94
+                angle = angle.value
  95
+
  96
+                unit = cls._convert_unit_to_angle_unit(unit)
  97
+        elif isinstance(angle, basestring):
  98
+            angle, unit = util.parse_angle(angle, unit)
  99
+
  100
+        angle = cls._tuple_to_float(angle, unit)
  101
+
  102
+        try:
  103
+            angle = np.asarray(angle)
  104
+        except ValueError as e:
  105
+            raise TypeError(str(e))
  106
+
  107
+        if angle.dtype.type in (np.string_, np.unicode_):
  108
+            # We need to modify this value from within
  109
+            # convert_string_to_angle, and the only way to do that
  110
+            # across Python 2.6 - 3.3 is to use this "store it in a
  111
+            # list" trick.
  112
+            determined_unit = [unit]
  113
+
  114
+            def convert_string_to_angle(x):
  115
+                ang, new_unit = util.parse_angle(str(x), unit)
  116
+                if determined_unit[0] is None:
  117
+                    determined_unit[0] = new_unit
  118
+                    return cls._tuple_to_float(ang, unit)
  119
+                else:
  120
+                    return new_unit.to(
  121
+                        determined_unit[0], cls._tuple_to_float(ang, unit))
  122
+
  123
+            convert_string_to_angle_ufunc = np.vectorize(
  124
+                convert_string_to_angle,
  125
+                otypes=[np.float_])
  126
+            angle = convert_string_to_angle_ufunc(angle)
  127
+            unit = determined_unit[0]
  128
+
  129
+        elif angle.dtype.kind not in 'iuf':
  130
+            raise TypeError("Unsupported dtype '{0}'".format(angle.dtype))
56 131
 
57  
-    def __init__(self, angle, unit=None, bounds=(-360, 360)):
58  
-        from ..utils import isiterable
59  
-
60  
-        self._bounds = bounds
61  
-
62  
-        if isinstance(angle, Angle):
63  
-            angle = angle.radians
64  
-            unit = u.radian
65  
-
66  
-        self.is_array = (isiterable(angle) and
67  
-                         not isinstance(angle, basestring) and
68  
-                         not isinstance(angle, tuple))
  132
+        if unit is None:
  133
+            raise u.UnitsException("No unit was specified")
69 134
 
70  
-        # short circuit arrays for now
71  
-        if self.is_array:
72  
-            raise NotImplementedError("Angles as arrays are not yet supported.")
  135
+        bounded_angle, bounds = cls._bounds_check(angle, bounds, unit)
73 136
 
74  
-        # -------------------------------
75  
-        # unit validation and angle value
76  
-        # -------------------------------
77  
-        if unit is not None:
78  
-            unit = u.Unit(unit)
  137
+        self = super(Angle, cls).__new__(
  138
+            cls, bounded_angle, unit, dtype=dtype,
  139
+            equivalencies=equivalencies)
79 140
 
80  
-        if unit is u.hour:
81  
-            unit = u.hourangle
  141
+        self._bounds = bounds
82 142
 
83  
-        if isinstance(angle, basestring):
84  
-            angle, found_unit = util.parse_angle(angle, unit)
85  
-            unit = found_unit
  143
+        return self
86 144
 
  145
+    @staticmethod
  146
+    def _tuple_to_float(angle, unit):
  147
+        """
  148
+        Converts an angle represented as a 3-tuple into a floating
  149
+        point number in the given unit.
  150
+        """
87 151
         if isinstance(angle, tuple):
  152
+            # TODO: Numpy array of tuples?
88 153
             if unit is u.hourangle:
89 154
                 util.check_hms_ranges(*angle)
90 155
                 angle = util.hms_to_hours(*angle)
@@ -94,119 +159,233 @@ def __init__(self, angle, unit=None, bounds=(-360, 360)):
94 159
                 raise u.UnitsException(
95 160
                     "Can not parse '{0}' as unit '{1}'".format(
96 161
                         angle, unit))
97  
-
98  
-        if unit is None:
99  
-            raise u.UnitsException("No unit was specified in Angle initializer; the "
100  
-                "unit parameter should be an object from the  astropy.units "
101  
-                "module (e.g. 'from astropy import units as u', then use "
102  
-                "'u.degree').")
103  
-
104  
-        if self.is_array:
105  
-            pass  # already performed conversions to radians above
  162
+        return angle
  163
+
  164
+    @staticmethod
  165
+    def _get_default_bounds():
  166
+        return (Angle(-360, u.degree, bounds=None),
  167
+                Angle(360, u.degree, bounds=None))
  168
+
  169
+    @staticmethod
  170
+    def _bounds_check(angle, bounds, unit):
  171
+        def raise_error(original_angle, lower_angle, upper_angle, unit):
  172
+            raise BoundsError(
  173
+                "The angle(s) {0} falls outside of the specified "
  174
+                "bounds ({1}, {2}) (in {3})".format(
  175
+                    u.radian.to(unit, original_angle),
  176
+                    u.radian.to(unit, lower_angle),
  177
+                    u.radian.to(unit, upper_angle),
  178
+                    unit))
  179
+
  180
+        if bounds is None:
  181
+            return angle, None
  182
+
  183
+        if bounds == ():
  184
+            bounds = Angle._get_default_bounds()
106 185
         else: