1# -*- coding: utf-8 -*- 2# Licensed under a 3-clause BSD style license - see LICENSE.rst 3"""Test initialization and other aspects of Angle and subclasses""" 4 5import threading 6import warnings 7 8import numpy as np 9import pytest 10from numpy.testing import assert_allclose, assert_array_equal 11 12import astropy.units as u 13from astropy.coordinates.angles import Longitude, Latitude, Angle 14from astropy.coordinates.errors import ( 15 IllegalSecondError, IllegalMinuteError, IllegalHourError, 16 IllegalSecondWarning, IllegalMinuteWarning) 17from astropy.utils.exceptions import AstropyDeprecationWarning 18 19 20def test_create_angles(): 21 """ 22 Tests creating and accessing Angle objects 23 """ 24 25 ''' The "angle" is a fundamental object. The internal 26 representation is stored in radians, but this is transparent to the user. 27 Units *must* be specified rather than a default value be assumed. This is 28 as much for self-documenting code as anything else. 29 30 Angle objects simply represent a single angular coordinate. More specific 31 angular coordinates (e.g. Longitude, Latitude) are subclasses of Angle.''' 32 33 a1 = Angle(54.12412, unit=u.degree) 34 a2 = Angle("54.12412", unit=u.degree) 35 a3 = Angle("54:07:26.832", unit=u.degree) 36 a4 = Angle("54.12412 deg") 37 a5 = Angle("54.12412 degrees") 38 a6 = Angle("54.12412°") # because we like Unicode 39 a7 = Angle((54, 7, 26.832), unit=u.degree) 40 a8 = Angle("54°07'26.832\"") 41 # (deg,min,sec) *tuples* are acceptable, but lists/arrays are *not* 42 # because of the need to eventually support arrays of coordinates 43 a9 = Angle([54, 7, 26.832], unit=u.degree) 44 assert_allclose(a9.value, [54, 7, 26.832]) 45 assert a9.unit is u.degree 46 47 a10 = Angle(3.60827466667, unit=u.hour) 48 a11 = Angle("3:36:29.7888000120", unit=u.hour) 49 a12 = Angle((3, 36, 29.7888000120), unit=u.hour) # *must* be a tuple 50 # Regression test for #5001 51 a13 = Angle((3, 36, 29.7888000120), unit='hour') 52 53 Angle(0.944644098745, unit=u.radian) 54 55 with pytest.raises(u.UnitsError): 56 Angle(54.12412) 57 # raises an exception because this is ambiguous 58 59 with pytest.raises(u.UnitsError): 60 Angle(54.12412, unit=u.m) 61 62 with pytest.raises(ValueError): 63 Angle(12.34, unit="not a unit") 64 65 a14 = Angle("03h36m29.7888000120") # no trailing 's', but unambiguous 66 67 a15 = Angle("5h4m3s") # single digits, no decimal 68 assert a15.unit == u.hourangle 69 70 a16 = Angle("1 d") 71 a17 = Angle("1 degree") 72 73 assert a16.degree == 1 74 assert a17.degree == 1 75 76 a18 = Angle("54 07.4472", unit=u.degree) 77 a19 = Angle("54:07.4472", unit=u.degree) 78 a20 = Angle("54d07.4472m", unit=u.degree) 79 a21 = Angle("3h36m", unit=u.hour) 80 a22 = Angle("3.6h", unit=u.hour) 81 a23 = Angle("- 3h", unit=u.hour) 82 a24 = Angle("+ 3h", unit=u.hour) 83 84 # ensure the above angles that should match do 85 assert a1 == a2 == a3 == a4 == a5 == a6 == a7 == a8 == a18 == a19 == a20 86 assert_allclose(a1.radian, a2.radian) 87 assert_allclose(a2.degree, a3.degree) 88 assert_allclose(a3.radian, a4.radian) 89 assert_allclose(a4.radian, a5.radian) 90 assert_allclose(a5.radian, a6.radian) 91 assert_allclose(a6.radian, a7.radian) 92 93 assert_allclose(a10.degree, a11.degree) 94 assert a11 == a12 == a13 == a14 95 assert a21 == a22 96 assert a23 == -a24 97 98 # check for illegal ranges / values 99 with pytest.raises(IllegalSecondError): 100 a = Angle("12 32 99", unit=u.degree) 101 102 with pytest.raises(IllegalMinuteError): 103 a = Angle("12 99 23", unit=u.degree) 104 105 with pytest.raises(IllegalSecondError): 106 a = Angle("12 32 99", unit=u.hour) 107 108 with pytest.raises(IllegalMinuteError): 109 a = Angle("12 99 23", unit=u.hour) 110 111 with pytest.raises(IllegalHourError): 112 a = Angle("99 25 51.0", unit=u.hour) 113 114 with pytest.raises(ValueError): 115 a = Angle("12 25 51.0xxx", unit=u.hour) 116 117 with pytest.raises(ValueError): 118 a = Angle("12h34321m32.2s") 119 120 assert a1 is not None 121 122 123def test_angle_from_view(): 124 q = np.arange(3.) * u.deg 125 a = q.view(Angle) 126 assert type(a) is Angle 127 assert a.unit is q.unit 128 assert np.all(a == q) 129 130 q2 = np.arange(4) * u.m 131 with pytest.raises(u.UnitTypeError): 132 q2.view(Angle) 133 134 135def test_angle_ops(): 136 """ 137 Tests operations on Angle objects 138 """ 139 140 # Angles can be added and subtracted. Multiplication and division by a 141 # scalar is also permitted. A negative operator is also valid. All of 142 # these operate in a single dimension. Attempting to multiply or divide two 143 # Angle objects will return a quantity. An exception will be raised if it 144 # is attempted to store output with a non-angular unit in an Angle [#2718]. 145 146 a1 = Angle(3.60827466667, unit=u.hour) 147 a2 = Angle("54:07:26.832", unit=u.degree) 148 a1 + a2 # creates new Angle object 149 a1 - a2 150 -a1 151 152 assert_allclose((a1 * 2).hour, 2 * 3.6082746666700003) 153 assert abs((a1 / 3.123456).hour - 3.60827466667 / 3.123456) < 1e-10 154 155 # commutativity 156 assert (2 * a1).hour == (a1 * 2).hour 157 158 a3 = Angle(a1) # makes a *copy* of the object, but identical content as a1 159 assert_allclose(a1.radian, a3.radian) 160 assert a1 is not a3 161 162 a4 = abs(-a1) 163 assert a4.radian == a1.radian 164 165 a5 = Angle(5.0, unit=u.hour) 166 assert a5 > a1 167 assert a5 >= a1 168 assert a1 < a5 169 assert a1 <= a5 170 171 # check operations with non-angular result give Quantity. 172 a6 = Angle(45., u.degree) 173 a7 = a6 * a5 174 assert type(a7) is u.Quantity 175 176 # but those with angular result yield Angle. 177 # (a9 is regression test for #5327) 178 a8 = a1 + 1.*u.deg 179 assert type(a8) is Angle 180 a9 = 1.*u.deg + a1 181 assert type(a9) is Angle 182 183 with pytest.raises(TypeError): 184 a6 *= a5 185 186 with pytest.raises(TypeError): 187 a6 *= u.m 188 189 with pytest.raises(TypeError): 190 np.sin(a6, out=a6) 191 192 193def test_angle_methods(): 194 # Most methods tested as part of the Quantity tests. 195 # A few tests here which caused problems before: #8368 196 a = Angle([0., 2.], 'deg') 197 a_mean = a.mean() 198 assert type(a_mean) is Angle 199 assert a_mean == 1. * u.degree 200 a_std = a.std() 201 assert type(a_std) is Angle 202 assert a_std == 1. * u.degree 203 a_var = a.var() 204 assert type(a_var) is u.Quantity 205 assert a_var == 1. * u.degree ** 2 206 a_ptp = a.ptp() 207 assert type(a_ptp) is Angle 208 assert a_ptp == 2. * u.degree 209 a_max = a.max() 210 assert type(a_max) is Angle 211 assert a_max == 2. * u.degree 212 a_min = a.min() 213 assert type(a_min) is Angle 214 assert a_min == 0. * u.degree 215 216 217def test_angle_convert(): 218 """ 219 Test unit conversion of Angle objects 220 """ 221 angle = Angle("54.12412", unit=u.degree) 222 223 assert_allclose(angle.hour, 3.60827466667) 224 assert_allclose(angle.radian, 0.944644098745) 225 assert_allclose(angle.degree, 54.12412) 226 227 assert len(angle.hms) == 3 228 assert isinstance(angle.hms, tuple) 229 assert angle.hms[0] == 3 230 assert angle.hms[1] == 36 231 assert_allclose(angle.hms[2], 29.78879999999947) 232 # also check that the namedtuple attribute-style access works: 233 assert angle.hms.h == 3 234 assert angle.hms.m == 36 235 assert_allclose(angle.hms.s, 29.78879999999947) 236 237 assert len(angle.dms) == 3 238 assert isinstance(angle.dms, tuple) 239 assert angle.dms[0] == 54 240 assert angle.dms[1] == 7 241 assert_allclose(angle.dms[2], 26.831999999992036) 242 # also check that the namedtuple attribute-style access works: 243 assert angle.dms.d == 54 244 assert angle.dms.m == 7 245 assert_allclose(angle.dms.s, 26.831999999992036) 246 247 assert isinstance(angle.dms[0], float) 248 assert isinstance(angle.hms[0], float) 249 250 # now make sure dms and signed_dms work right for negative angles 251 negangle = Angle("-54.12412", unit=u.degree) 252 253 assert negangle.dms.d == -54 254 assert negangle.dms.m == -7 255 assert_allclose(negangle.dms.s, -26.831999999992036) 256 assert negangle.signed_dms.sign == -1 257 assert negangle.signed_dms.d == 54 258 assert negangle.signed_dms.m == 7 259 assert_allclose(negangle.signed_dms.s, 26.831999999992036) 260 261 262def test_angle_formatting(): 263 """ 264 Tests string formatting for Angle objects 265 """ 266 267 ''' 268 The string method of Angle has this signature: 269 def string(self, unit=DEGREE, decimal=False, sep=" ", precision=5, 270 pad=False): 271 272 The "decimal" parameter defaults to False since if you need to print the 273 Angle as a decimal, there's no need to use the "format" method (see 274 above). 275 ''' 276 277 angle = Angle("54.12412", unit=u.degree) 278 279 # __str__ is the default `format` 280 assert str(angle) == angle.to_string() 281 282 res = 'Angle as HMS: 3h36m29.7888s' 283 assert f"Angle as HMS: {angle.to_string(unit=u.hour)}" == res 284 285 res = 'Angle as HMS: 3:36:29.7888' 286 assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':')}" == res 287 288 res = 'Angle as HMS: 3:36:29.79' 289 assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':', precision=2)}" == res 290 291 # Note that you can provide one, two, or three separators passed as a 292 # tuple or list 293 294 res = 'Angle as HMS: 3h36m29.7888s' 295 assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour, 296 sep=("h", "m", "s"), 297 precision=4)) == res 298 299 res = 'Angle as HMS: 3-36|29.7888' 300 assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour, sep=["-", "|"], 301 precision=4)) == res 302 303 res = 'Angle as HMS: 3-36-29.7888' 304 assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep='-', precision=4)}" == res 305 306 res = 'Angle as HMS: 03h36m29.7888s' 307 assert f"Angle as HMS: {angle.to_string(unit=u.hour, precision=4, pad=True)}" == res 308 309 # Same as above, in degrees 310 311 angle = Angle("3 36 29.78880", unit=u.degree) 312 313 res = 'Angle as DMS: 3d36m29.7888s' 314 assert f"Angle as DMS: {angle.to_string(unit=u.degree)}" == res 315 316 res = 'Angle as DMS: 3:36:29.7888' 317 assert f"Angle as DMS: {angle.to_string(unit=u.degree, sep=':')}" == res 318 319 res = 'Angle as DMS: 3:36:29.79' 320 assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=":", 321 precision=2)) == res 322 323 # Note that you can provide one, two, or three separators passed as a 324 # tuple or list 325 326 res = 'Angle as DMS: 3d36m29.7888s' 327 assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, 328 sep=("d", "m", "s"), 329 precision=4)) == res 330 331 res = 'Angle as DMS: 3-36|29.7888' 332 assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=["-", "|"], 333 precision=4)) == res 334 335 res = 'Angle as DMS: 3-36-29.7888' 336 assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep="-", 337 precision=4)) == res 338 339 res = 'Angle as DMS: 03d36m29.7888s' 340 assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, precision=4, 341 pad=True)) == res 342 343 res = 'Angle as rad: 0.0629763rad' 344 assert f"Angle as rad: {angle.to_string(unit=u.radian)}" == res 345 346 res = 'Angle as rad decimal: 0.0629763' 347 assert f"Angle as rad decimal: {angle.to_string(unit=u.radian, decimal=True)}" == res 348 349 # check negative angles 350 351 angle = Angle(-1.23456789, unit=u.degree) 352 angle2 = Angle(-1.23456789, unit=u.hour) 353 354 assert angle.to_string() == '-1d14m04.444404s' 355 assert angle.to_string(pad=True) == '-01d14m04.444404s' 356 assert angle.to_string(unit=u.hour) == '-0h04m56.2962936s' 357 assert angle2.to_string(unit=u.hour, pad=True) == '-01h14m04.444404s' 358 assert angle.to_string(unit=u.radian, decimal=True) == '-0.0215473' 359 360 361def test_to_string_vector(): 362 # Regression test for the fact that vectorize doesn't work with Numpy 1.6 363 assert Angle([1./7., 1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s" 364 assert Angle([1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s" 365 assert Angle(1./7., unit='deg').to_string() == "0d08m34.28571429s" 366 367 368def test_angle_format_roundtripping(): 369 """ 370 Ensures that the string representation of an angle can be used to create a 371 new valid Angle. 372 """ 373 374 a1 = Angle(0, unit=u.radian) 375 a2 = Angle(10, unit=u.degree) 376 a3 = Angle(0.543, unit=u.degree) 377 a4 = Angle('1d2m3.4s') 378 379 assert Angle(str(a1)).degree == a1.degree 380 assert Angle(str(a2)).degree == a2.degree 381 assert Angle(str(a3)).degree == a3.degree 382 assert Angle(str(a4)).degree == a4.degree 383 384 # also check Longitude/Latitude 385 ra = Longitude('1h2m3.4s') 386 dec = Latitude('1d2m3.4s') 387 388 assert_allclose(Angle(str(ra)).degree, ra.degree) 389 assert_allclose(Angle(str(dec)).degree, dec.degree) 390 391 392def test_radec(): 393 """ 394 Tests creation/operations of Longitude and Latitude objects 395 """ 396 397 ''' 398 Longitude and Latitude are objects that are subclassed from Angle. As with Angle, Longitude 399 and Latitude can parse any unambiguous format (tuples, formatted strings, etc.). 400 401 The intention is not to create an Angle subclass for every possible 402 coordinate object (e.g. galactic l, galactic b). However, equatorial Longitude/Latitude 403 are so prevalent in astronomy that it's worth creating ones for these 404 units. They will be noted as "special" in the docs and use of the just the 405 Angle class is to be used for other coordinate systems. 406 ''' 407 408 with pytest.raises(u.UnitsError): 409 ra = Longitude("4:08:15.162342") # error - hours or degrees? 410 with pytest.raises(u.UnitsError): 411 ra = Longitude("-4:08:15.162342") 412 413 # the "smart" initializer allows >24 to automatically do degrees, but the 414 # Angle-based one does not 415 # TODO: adjust in 0.3 for whatever behavior is decided on 416 417 # ra = Longitude("26:34:15.345634") # unambiguous b/c hours don't go past 24 418 # assert_allclose(ra.degree, 26.570929342) 419 with pytest.raises(u.UnitsError): 420 ra = Longitude("26:34:15.345634") 421 422 # ra = Longitude(68) 423 with pytest.raises(u.UnitsError): 424 ra = Longitude(68) 425 426 with pytest.raises(u.UnitsError): 427 ra = Longitude(12) 428 429 with pytest.raises(ValueError): 430 ra = Longitude("garbage containing a d and no units") 431 432 ra = Longitude("12h43m23s") 433 assert_allclose(ra.hour, 12.7230555556) 434 435 ra = Longitude((56, 14, 52.52), unit=u.degree) # can accept tuples 436 # TODO: again, fix based on >24 behavior 437 # ra = Longitude((56,14,52.52)) 438 with pytest.raises(u.UnitsError): 439 ra = Longitude((56, 14, 52.52)) 440 with pytest.raises(u.UnitsError): 441 ra = Longitude((12, 14, 52)) # ambiguous w/o units 442 ra = Longitude((12, 14, 52), unit=u.hour) 443 444 ra = Longitude([56, 64, 52.2], unit=u.degree) # ...but not arrays (yet) 445 446 # Units can be specified 447 ra = Longitude("4:08:15.162342", unit=u.hour) 448 449 # TODO: this was the "smart" initializer behavior - adjust in 0.3 appropriately 450 # Where Longitude values are commonly found in hours or degrees, declination is 451 # nearly always specified in degrees, so this is the default. 452 # dec = Latitude("-41:08:15.162342") 453 with pytest.raises(u.UnitsError): 454 dec = Latitude("-41:08:15.162342") 455 dec = Latitude("-41:08:15.162342", unit=u.degree) # same as above 456 457 458def test_negative_zero_dms(): 459 # Test for DMS parser 460 a = Angle('-00:00:10', u.deg) 461 assert_allclose(a.degree, -10. / 3600.) 462 463 # Unicode minus 464 a = Angle('−00:00:10', u.deg) 465 assert_allclose(a.degree, -10. / 3600.) 466 467 468def test_negative_zero_dm(): 469 # Test for DM parser 470 a = Angle('-00:10', u.deg) 471 assert_allclose(a.degree, -10. / 60.) 472 473 474def test_negative_zero_hms(): 475 # Test for HMS parser 476 a = Angle('-00:00:10', u.hour) 477 assert_allclose(a.hour, -10. / 3600.) 478 479 480def test_negative_zero_hm(): 481 # Test for HM parser 482 a = Angle('-00:10', u.hour) 483 assert_allclose(a.hour, -10. / 60.) 484 485 486def test_negative_sixty_hm(): 487 # Test for HM parser 488 with pytest.warns(IllegalMinuteWarning): 489 a = Angle('-00:60', u.hour) 490 assert_allclose(a.hour, -1.) 491 492 493def test_plus_sixty_hm(): 494 # Test for HM parser 495 with pytest.warns(IllegalMinuteWarning): 496 a = Angle('00:60', u.hour) 497 assert_allclose(a.hour, 1.) 498 499 500def test_negative_fifty_nine_sixty_dms(): 501 # Test for DMS parser 502 with pytest.warns(IllegalSecondWarning): 503 a = Angle('-00:59:60', u.deg) 504 assert_allclose(a.degree, -1.) 505 506 507def test_plus_fifty_nine_sixty_dms(): 508 # Test for DMS parser 509 with pytest.warns(IllegalSecondWarning): 510 a = Angle('+00:59:60', u.deg) 511 assert_allclose(a.degree, 1.) 512 513 514def test_negative_sixty_dms(): 515 # Test for DMS parser 516 with pytest.warns(IllegalSecondWarning): 517 a = Angle('-00:00:60', u.deg) 518 assert_allclose(a.degree, -1. / 60.) 519 520 521def test_plus_sixty_dms(): 522 # Test for DMS parser 523 with pytest.warns(IllegalSecondWarning): 524 a = Angle('+00:00:60', u.deg) 525 assert_allclose(a.degree, 1. / 60.) 526 527 528def test_angle_to_is_angle(): 529 with pytest.warns(IllegalSecondWarning): 530 a = Angle('00:00:60', u.deg) 531 assert isinstance(a, Angle) 532 assert isinstance(a.to(u.rad), Angle) 533 534 535def test_angle_to_quantity(): 536 with pytest.warns(IllegalSecondWarning): 537 a = Angle('00:00:60', u.deg) 538 q = u.Quantity(a) 539 assert isinstance(q, u.Quantity) 540 assert q.unit is u.deg 541 542 543def test_quantity_to_angle(): 544 a = Angle(1.0*u.deg) 545 assert isinstance(a, Angle) 546 with pytest.raises(u.UnitsError): 547 Angle(1.0*u.meter) 548 a = Angle(1.0*u.hour) 549 assert isinstance(a, Angle) 550 assert a.unit is u.hourangle 551 with pytest.raises(u.UnitsError): 552 Angle(1.0*u.min) 553 554 555def test_angle_string(): 556 with pytest.warns(IllegalSecondWarning): 557 a = Angle('00:00:60', u.deg) 558 assert str(a) == '0d01m00s' 559 a = Angle('00:00:59S', u.deg) 560 assert str(a) == '-0d00m59s' 561 a = Angle('00:00:59N', u.deg) 562 assert str(a) == '0d00m59s' 563 a = Angle('00:00:59E', u.deg) 564 assert str(a) == '0d00m59s' 565 a = Angle('00:00:59W', u.deg) 566 assert str(a) == '-0d00m59s' 567 a = Angle('-00:00:10', u.hour) 568 assert str(a) == '-0h00m10s' 569 a = Angle('00:00:59E', u.hour) 570 assert str(a) == '0h00m59s' 571 a = Angle('00:00:59W', u.hour) 572 assert str(a) == '-0h00m59s' 573 a = Angle(3.2, u.radian) 574 assert str(a) == '3.2rad' 575 a = Angle(4.2, u.microarcsecond) 576 assert str(a) == '4.2uarcsec' 577 a = Angle('1.0uarcsec') 578 assert a.value == 1.0 579 assert a.unit == u.microarcsecond 580 a = Angle('1.0uarcsecN') 581 assert a.value == 1.0 582 assert a.unit == u.microarcsecond 583 a = Angle('1.0uarcsecS') 584 assert a.value == -1.0 585 assert a.unit == u.microarcsecond 586 a = Angle('1.0uarcsecE') 587 assert a.value == 1.0 588 assert a.unit == u.microarcsecond 589 a = Angle('1.0uarcsecW') 590 assert a.value == -1.0 591 assert a.unit == u.microarcsecond 592 a = Angle("3d") 593 assert_allclose(a.value, 3.0) 594 assert a.unit == u.degree 595 a = Angle("3dN") 596 assert str(a) == "3d00m00s" 597 assert a.unit == u.degree 598 a = Angle("3dS") 599 assert str(a) == "-3d00m00s" 600 assert a.unit == u.degree 601 a = Angle("3dE") 602 assert str(a) == "3d00m00s" 603 assert a.unit == u.degree 604 a = Angle("3dW") 605 assert str(a) == "-3d00m00s" 606 assert a.unit == u.degree 607 a = Angle('10"') 608 assert_allclose(a.value, 10.0) 609 assert a.unit == u.arcsecond 610 a = Angle("10'N") 611 assert_allclose(a.value, 10.0) 612 assert a.unit == u.arcminute 613 a = Angle("10'S") 614 assert_allclose(a.value, -10.0) 615 assert a.unit == u.arcminute 616 a = Angle("10'E") 617 assert_allclose(a.value, 10.0) 618 assert a.unit == u.arcminute 619 a = Angle("10'W") 620 assert_allclose(a.value, -10.0) 621 assert a.unit == u.arcminute 622 a = Angle('45°55′12″N') 623 assert str(a) == '45d55m12s' 624 assert_allclose(a.value, 45.92) 625 assert a.unit == u.deg 626 a = Angle('45°55′12″S') 627 assert str(a) == '-45d55m12s' 628 assert_allclose(a.value, -45.92) 629 assert a.unit == u.deg 630 a = Angle('45°55′12″E') 631 assert str(a) == '45d55m12s' 632 assert_allclose(a.value, 45.92) 633 assert a.unit == u.deg 634 a = Angle('45°55′12″W') 635 assert str(a) == '-45d55m12s' 636 assert_allclose(a.value, -45.92) 637 assert a.unit == u.deg 638 with pytest.raises(ValueError): 639 Angle('00h00m10sN') 640 with pytest.raises(ValueError): 641 Angle('45°55′12″NS') 642 643 644def test_angle_repr(): 645 assert 'Angle' in repr(Angle(0, u.deg)) 646 assert 'Longitude' in repr(Longitude(0, u.deg)) 647 assert 'Latitude' in repr(Latitude(0, u.deg)) 648 649 a = Angle(0, u.deg) 650 repr(a) 651 652 653def test_large_angle_representation(): 654 """Test that angles above 360 degrees can be output as strings, 655 in repr, str, and to_string. (regression test for #1413)""" 656 a = Angle(350, u.deg) + Angle(350, u.deg) 657 a.to_string() 658 a.to_string(u.hourangle) 659 repr(a) 660 repr(a.to(u.hourangle)) 661 str(a) 662 str(a.to(u.hourangle)) 663 664 665def test_wrap_at_inplace(): 666 a = Angle([-20, 150, 350, 360] * u.deg) 667 out = a.wrap_at('180d', inplace=True) 668 assert out is None 669 assert np.all(a.degree == np.array([-20., 150., -10., 0.])) 670 671 672def test_latitude(): 673 with pytest.raises(ValueError): 674 lat = Latitude(['91d', '89d']) 675 with pytest.raises(ValueError): 676 lat = Latitude('-91d') 677 678 lat = Latitude(['90d', '89d']) 679 # check that one can get items 680 assert lat[0] == 90 * u.deg 681 assert lat[1] == 89 * u.deg 682 # and that comparison with angles works 683 assert np.all(lat == Angle(['90d', '89d'])) 684 # check setitem works 685 lat[1] = 45. * u.deg 686 assert np.all(lat == Angle(['90d', '45d'])) 687 # but not with values out of range 688 with pytest.raises(ValueError): 689 lat[0] = 90.001 * u.deg 690 with pytest.raises(ValueError): 691 lat[0] = -90.001 * u.deg 692 # these should also not destroy input (#1851) 693 assert np.all(lat == Angle(['90d', '45d'])) 694 695 # conserve type on unit change (closes #1423) 696 angle = lat.to('radian') 697 assert type(angle) is Latitude 698 # but not on calculations 699 angle = lat - 190 * u.deg 700 assert type(angle) is Angle 701 assert angle[0] == -100 * u.deg 702 703 lat = Latitude('80d') 704 angle = lat / 2. 705 assert type(angle) is Angle 706 assert angle == 40 * u.deg 707 708 angle = lat * 2. 709 assert type(angle) is Angle 710 assert angle == 160 * u.deg 711 712 angle = -lat 713 assert type(angle) is Angle 714 assert angle == -80 * u.deg 715 716 # Test errors when trying to interoperate with longitudes. 717 with pytest.raises(TypeError) as excinfo: 718 lon = Longitude(10, 'deg') 719 lat = Latitude(lon) 720 assert "A Latitude angle cannot be created from a Longitude angle" in str(excinfo.value) 721 722 with pytest.raises(TypeError) as excinfo: 723 lon = Longitude(10, 'deg') 724 lat = Latitude([20], 'deg') 725 lat[0] = lon 726 assert "A Longitude angle cannot be assigned to a Latitude angle" in str(excinfo.value) 727 728 # Check we can work around the Lat vs Long checks by casting explicitly to Angle. 729 lon = Longitude(10, 'deg') 730 lat = Latitude(Angle(lon)) 731 assert lat.value == 10.0 732 # Check setitem. 733 lon = Longitude(10, 'deg') 734 lat = Latitude([20], 'deg') 735 lat[0] = Angle(lon) 736 assert lat.value[0] == 10.0 737 738 739def test_longitude(): 740 # Default wrapping at 360d with an array input 741 lon = Longitude(['370d', '88d']) 742 assert np.all(lon == Longitude(['10d', '88d'])) 743 assert np.all(lon == Angle(['10d', '88d'])) 744 745 # conserve type on unit change and keep wrap_angle (closes #1423) 746 angle = lon.to('hourangle') 747 assert type(angle) is Longitude 748 assert angle.wrap_angle == lon.wrap_angle 749 angle = lon[0] 750 assert type(angle) is Longitude 751 assert angle.wrap_angle == lon.wrap_angle 752 angle = lon[1:] 753 assert type(angle) is Longitude 754 assert angle.wrap_angle == lon.wrap_angle 755 756 # but not on calculations 757 angle = lon / 2. 758 assert np.all(angle == Angle(['5d', '44d'])) 759 assert type(angle) is Angle 760 assert not hasattr(angle, 'wrap_angle') 761 762 angle = lon * 2. + 400 * u.deg 763 assert np.all(angle == Angle(['420d', '576d'])) 764 assert type(angle) is Angle 765 766 # Test setting a mutable value and having it wrap 767 lon[1] = -10 * u.deg 768 assert np.all(lon == Angle(['10d', '350d'])) 769 770 # Test wrapping and try hitting some edge cases 771 lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) 772 assert np.all(lon.degree == np.array([0., 90, 180, 270, 0])) 773 774 lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle='180d') 775 assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) 776 777 # Wrap on setting wrap_angle property (also test auto-conversion of wrap_angle to an Angle) 778 lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian) 779 lon.wrap_angle = '180d' 780 assert np.all(lon.degree == np.array([0., 90, -180, -90, 0])) 781 782 lon = Longitude('460d') 783 assert lon == Angle('100d') 784 lon.wrap_angle = '90d' 785 assert lon == Angle('-260d') 786 787 # check that if we initialize a longitude with another longitude, 788 # wrap_angle is kept by default 789 lon2 = Longitude(lon) 790 assert lon2.wrap_angle == lon.wrap_angle 791 # but not if we explicitly set it 792 lon3 = Longitude(lon, wrap_angle='180d') 793 assert lon3.wrap_angle == 180 * u.deg 794 795 # check that wrap_angle is always an Angle 796 lon = Longitude(lon, wrap_angle=Longitude(180 * u.deg)) 797 assert lon.wrap_angle == 180 * u.deg 798 assert lon.wrap_angle.__class__ is Angle 799 800 # check that wrap_angle is not copied 801 wrap_angle=180 * u.deg 802 lon = Longitude(lon, wrap_angle=wrap_angle) 803 assert lon.wrap_angle == 180 * u.deg 804 assert np.may_share_memory(lon.wrap_angle, wrap_angle) 805 806 # check for problem reported in #2037 about Longitude initializing to -0 807 lon = Longitude(0, u.deg) 808 lonstr = lon.to_string() 809 assert not lonstr.startswith('-') 810 811 # also make sure dtype is correctly conserved 812 assert Longitude(0, u.deg, dtype=float).dtype == np.dtype(float) 813 assert Longitude(0, u.deg, dtype=int).dtype == np.dtype(int) 814 815 # Test errors when trying to interoperate with latitudes. 816 with pytest.raises(TypeError) as excinfo: 817 lat = Latitude(10, 'deg') 818 lon = Longitude(lat) 819 assert "A Longitude angle cannot be created from a Latitude angle" in str(excinfo.value) 820 821 with pytest.raises(TypeError) as excinfo: 822 lat = Latitude(10, 'deg') 823 lon = Longitude([20], 'deg') 824 lon[0] = lat 825 assert "A Latitude angle cannot be assigned to a Longitude angle" in str(excinfo.value) 826 827 # Check we can work around the Lat vs Long checks by casting explicitly to Angle. 828 lat = Latitude(10, 'deg') 829 lon = Longitude(Angle(lat)) 830 assert lon.value == 10.0 831 # Check setitem. 832 lat = Latitude(10, 'deg') 833 lon = Longitude([20], 'deg') 834 lon[0] = Angle(lat) 835 assert lon.value[0] == 10.0 836 837 838def test_wrap_at(): 839 a = Angle([-20, 150, 350, 360] * u.deg) 840 assert np.all(a.wrap_at(360 * u.deg).degree == np.array([340., 150., 350., 0.])) 841 assert np.all(a.wrap_at(Angle(360, unit=u.deg)).degree == np.array([340., 150., 350., 0.])) 842 assert np.all(a.wrap_at('360d').degree == np.array([340., 150., 350., 0.])) 843 assert np.all(a.wrap_at('180d').degree == np.array([-20., 150., -10., 0.])) 844 assert np.all(a.wrap_at(np.pi * u.rad).degree == np.array([-20., 150., -10., 0.])) 845 846 # Test wrapping a scalar Angle 847 a = Angle('190d') 848 assert a.wrap_at('180d') == Angle('-170d') 849 850 a = Angle(np.arange(-1000.0, 1000.0, 0.125), unit=u.deg) 851 for wrap_angle in (270, 0.2, 0.0, 360.0, 500, -2000.125): 852 aw = a.wrap_at(wrap_angle * u.deg) 853 assert np.all(aw.degree >= wrap_angle - 360.0) 854 assert np.all(aw.degree < wrap_angle) 855 856 aw = a.to(u.rad).wrap_at(wrap_angle * u.deg) 857 assert np.all(aw.degree >= wrap_angle - 360.0) 858 assert np.all(aw.degree < wrap_angle) 859 860 861def test_is_within_bounds(): 862 a = Angle([-20, 150, 350] * u.deg) 863 assert a.is_within_bounds('0d', '360d') is False 864 assert a.is_within_bounds(None, '360d') is True 865 assert a.is_within_bounds(-30 * u.deg, None) is True 866 867 a = Angle('-20d') 868 assert a.is_within_bounds('0d', '360d') is False 869 assert a.is_within_bounds(None, '360d') is True 870 assert a.is_within_bounds(-30 * u.deg, None) is True 871 872 873def test_angle_mismatched_unit(): 874 a = Angle('+6h7m8s', unit=u.degree) 875 assert_allclose(a.value, 91.78333333333332) 876 877 878def test_regression_formatting_negative(): 879 # Regression test for a bug that caused: 880 # 881 # >>> Angle(-1., unit='deg').to_string() 882 # '-1d00m-0s' 883 assert Angle(-0., unit='deg').to_string() == '-0d00m00s' 884 assert Angle(-1., unit='deg').to_string() == '-1d00m00s' 885 assert Angle(-0., unit='hour').to_string() == '-0h00m00s' 886 assert Angle(-1., unit='hour').to_string() == '-1h00m00s' 887 888 889def test_regression_formatting_default_precision(): 890 # Regression test for issue #11140 891 assert Angle('10:20:30.12345678d').to_string() == '10d20m30.12345678s' 892 assert Angle('10d20m30.123456784564s').to_string() == '10d20m30.12345678s' 893 assert Angle('10d20m30.123s').to_string() == '10d20m30.123s' 894 895 896def test_empty_sep(): 897 a = Angle('05h04m31.93830s') 898 899 assert a.to_string(sep='', precision=2, pad=True) == '050431.94' 900 901 902def test_create_tuple(): 903 """ 904 Tests creation of an angle with a (d,m,s) or (h,m,s) tuple 905 """ 906 a1 = Angle((1, 30, 0), unit=u.degree) 907 assert a1.value == 1.5 908 909 a1 = Angle((1, 30, 0), unit=u.hourangle) 910 assert a1.value == 1.5 911 912 913def test_list_of_quantities(): 914 a1 = Angle([1*u.deg, 1*u.hourangle]) 915 assert a1.unit == u.deg 916 assert_allclose(a1.value, [1, 15]) 917 918 a2 = Angle([1*u.hourangle, 1*u.deg], u.deg) 919 assert a2.unit == u.deg 920 assert_allclose(a2.value, [15, 1]) 921 922 923def test_multiply_divide(): 924 # Issue #2273 925 a1 = Angle([1, 2, 3], u.deg) 926 a2 = Angle([4, 5, 6], u.deg) 927 a3 = a1 * a2 928 assert_allclose(a3.value, [4, 10, 18]) 929 assert a3.unit == (u.deg * u.deg) 930 931 a3 = a1 / a2 932 assert_allclose(a3.value, [.25, .4, .5]) 933 assert a3.unit == u.dimensionless_unscaled 934 935 936def test_mixed_string_and_quantity(): 937 a1 = Angle(['1d', 1. * u.deg]) 938 assert_array_equal(a1.value, [1., 1.]) 939 assert a1.unit == u.deg 940 941 a2 = Angle(['1d', 1 * u.rad * np.pi, '3d']) 942 assert_array_equal(a2.value, [1., 180., 3.]) 943 assert a2.unit == u.deg 944 945 946def test_array_angle_tostring(): 947 aobj = Angle([1, 2], u.deg) 948 assert aobj.to_string().dtype.kind == 'U' 949 assert np.all(aobj.to_string() == ['1d00m00s', '2d00m00s']) 950 951 952def test_wrap_at_without_new(): 953 """ 954 Regression test for subtle bugs from situations where an Angle is 955 created via numpy channels that don't do the standard __new__ but instead 956 depend on array_finalize to set state. Longitude is used because the 957 bug was in its _wrap_angle not getting initialized correctly 958 """ 959 l1 = Longitude([1]*u.deg) 960 l2 = Longitude([2]*u.deg) 961 962 l = np.concatenate([l1, l2]) 963 assert l._wrap_angle is not None 964 965 966def test__str__(): 967 """ 968 Check the __str__ method used in printing the Angle 969 """ 970 971 # scalar angle 972 scangle = Angle('10.2345d') 973 strscangle = scangle.__str__() 974 assert strscangle == '10d14m04.2s' 975 976 # non-scalar array angles 977 arrangle = Angle(['10.2345d', '-20d']) 978 strarrangle = arrangle.__str__() 979 980 assert strarrangle == '[10d14m04.2s -20d00m00s]' 981 982 # summarizing for large arrays, ... should appear 983 bigarrangle = Angle(np.ones(10000), u.deg) 984 assert '...' in bigarrangle.__str__() 985 986 987def test_repr_latex(): 988 """ 989 Check the _repr_latex_ method, used primarily by IPython notebooks 990 """ 991 992 # try with both scalar 993 scangle = Angle(2.1, u.deg) 994 rlscangle = scangle._repr_latex_() 995 996 # and array angles 997 arrangle = Angle([1, 2.1], u.deg) 998 rlarrangle = arrangle._repr_latex_() 999 1000 assert rlscangle == r'$2^\circ06{}^\prime00{}^{\prime\prime}$' 1001 assert rlscangle.split('$')[1] in rlarrangle 1002 1003 # make sure the ... appears for large arrays 1004 bigarrangle = Angle(np.ones(50000)/50000., u.deg) 1005 assert '...' in bigarrangle._repr_latex_() 1006 1007 1008def test_angle_with_cds_units_enabled(): 1009 """Regression test for #5350 1010 1011 Especially the example in 1012 https://github.com/astropy/astropy/issues/5350#issuecomment-248770151 1013 """ 1014 from astropy.units import cds 1015 # the problem is with the parser, so remove it temporarily 1016 from astropy.coordinates.angle_formats import _AngleParser 1017 del _AngleParser._thread_local._parser 1018 with cds.enable(): 1019 Angle('5d') 1020 del _AngleParser._thread_local._parser 1021 Angle('5d') 1022 1023 1024def test_longitude_nan(): 1025 # Check that passing a NaN to Longitude doesn't raise a warning 1026 Longitude([0, np.nan, 1] * u.deg) 1027 1028 1029def test_latitude_nan(): 1030 # Check that passing a NaN to Latitude doesn't raise a warning 1031 Latitude([0, np.nan, 1] * u.deg) 1032 1033 1034def test_angle_wrap_at_nan(): 1035 # Check that no attempt is made to wrap a NaN angle 1036 angle = Angle([0, np.nan, 1] * u.deg) 1037 angle.flags.writeable = False # to force an error if a write is attempted 1038 angle.wrap_at(180*u.deg, inplace=True) 1039 1040 1041def test_angle_multithreading(): 1042 """ 1043 Regression test for issue #7168 1044 """ 1045 angles = ['00:00:00']*10000 1046 1047 def parse_test(i=0): 1048 Angle(angles, unit='hour') 1049 for i in range(10): 1050 threading.Thread(target=parse_test, args=(i,)).start() 1051 1052 1053@pytest.mark.parametrize("cls", [Angle, Longitude, Latitude]) 1054@pytest.mark.parametrize("input, expstr, exprepr", 1055 [(np.nan*u.deg, 1056 "nan", 1057 "nan deg"), 1058 ([np.nan, 5, 0]*u.deg, 1059 "[nan 5d00m00s 0d00m00s]", 1060 "[nan, 5., 0.] deg"), 1061 ([6, np.nan, 0]*u.deg, 1062 "[6d00m00s nan 0d00m00s]", 1063 "[6., nan, 0.] deg"), 1064 ([np.nan, np.nan, np.nan]*u.deg, 1065 "[nan nan nan]", 1066 "[nan, nan, nan] deg"), 1067 (np.nan*u.hour, 1068 "nan", 1069 "nan hourangle"), 1070 ([np.nan, 5, 0]*u.hour, 1071 "[nan 5h00m00s 0h00m00s]", 1072 "[nan, 5., 0.] hourangle"), 1073 ([6, np.nan, 0]*u.hour, 1074 "[6h00m00s nan 0h00m00s]", 1075 "[6., nan, 0.] hourangle"), 1076 ([np.nan, np.nan, np.nan]*u.hour, 1077 "[nan nan nan]", 1078 "[nan, nan, nan] hourangle"), 1079 (np.nan*u.rad, 1080 "nan", 1081 "nan rad"), 1082 ([np.nan, 1, 0]*u.rad, 1083 "[nan 1rad 0rad]", 1084 "[nan, 1., 0.] rad"), 1085 ([1.50, np.nan, 0]*u.rad, 1086 "[1.5rad nan 0rad]", 1087 "[1.5, nan, 0.] rad"), 1088 ([np.nan, np.nan, np.nan]*u.rad, 1089 "[nan nan nan]", 1090 "[nan, nan, nan] rad")]) 1091def test_str_repr_angles_nan(cls, input, expstr, exprepr): 1092 """ 1093 Regression test for issue #11473 1094 """ 1095 q = cls(input) 1096 assert str(q) == expstr 1097 # Deleting whitespaces since repr appears to be adding them for some values 1098 # making the test fail. 1099 assert repr(q).replace(" ", "") == f'<{cls.__name__}{exprepr}>'.replace(" ","") 1100