1# -*- coding: utf-8 -*- 2# Licensed under a 3-clause BSD style license - see LICENSE.rst 3 4from copy import deepcopy 5 6import pytest 7import numpy as np 8from numpy.testing import assert_allclose, assert_array_equal 9 10from astropy import units as u 11from astropy.tests.helper import (assert_quantity_allclose as 12 assert_allclose_quantity) 13from astropy.utils import isiterable 14from astropy.utils.exceptions import DuplicateRepresentationWarning 15from astropy.coordinates.angles import Longitude, Latitude, Angle 16from astropy.coordinates.distances import Distance 17from astropy.coordinates.matrix_utilities import rotation_matrix 18from astropy.coordinates.representation import ( 19 REPRESENTATION_CLASSES, DIFFERENTIAL_CLASSES, DUPLICATE_REPRESENTATIONS, 20 BaseRepresentation, SphericalRepresentation, UnitSphericalRepresentation, 21 SphericalCosLatDifferential, CartesianRepresentation, RadialRepresentation, 22 RadialDifferential, CylindricalRepresentation, 23 PhysicsSphericalRepresentation, CartesianDifferential, 24 SphericalDifferential, CylindricalDifferential, 25 PhysicsSphericalDifferential, UnitSphericalDifferential, 26 UnitSphericalCosLatDifferential) 27 28 29# create matrices for use in testing ``.transform()`` methods 30matrices = { 31 "rotation": rotation_matrix(-10, "z", u.deg), 32 "general": np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 33} 34 35 36# Preserve the original REPRESENTATION_CLASSES dict so that importing 37# the test file doesn't add a persistent test subclass (LogDRepresentation) 38def setup_function(func): 39 func.REPRESENTATION_CLASSES_ORIG = deepcopy(REPRESENTATION_CLASSES) 40 func.DUPLICATE_REPRESENTATIONS_ORIG = deepcopy(DUPLICATE_REPRESENTATIONS) 41 42 43def teardown_function(func): 44 REPRESENTATION_CLASSES.clear() 45 REPRESENTATION_CLASSES.update(func.REPRESENTATION_CLASSES_ORIG) 46 DUPLICATE_REPRESENTATIONS.clear() 47 DUPLICATE_REPRESENTATIONS.update(func.DUPLICATE_REPRESENTATIONS_ORIG) 48 49 50def components_equal(rep1, rep2): 51 result = True 52 if type(rep1) is not type(rep2): 53 return False 54 for component in rep1.components: 55 result &= getattr(rep1, component) == getattr(rep2, component) 56 return result 57 58 59def components_allclose(rep1, rep2): 60 result = True 61 if type(rep1) is not type(rep2): 62 return False 63 for component in rep1.components: 64 result &= u.allclose(getattr(rep1, component), getattr(rep2, component)) 65 return result 66 67 68def representation_equal(rep1, rep2): 69 result = True 70 if type(rep1) is not type(rep2): 71 return False 72 if getattr(rep1, '_differentials', False): 73 if rep1._differentials.keys() != rep2._differentials.keys(): 74 return False 75 for key, diff1 in rep1._differentials.items(): 76 result &= components_equal(diff1, rep2._differentials[key]) 77 elif getattr(rep2, '_differentials', False): 78 return False 79 80 return result & components_equal(rep1, rep2) 81 82 83def representation_equal_up_to_angular_type(rep1, rep2): 84 result = True 85 if type(rep1) is not type(rep2): 86 return False 87 if getattr(rep1, '_differentials', False): 88 if rep1._differentials.keys() != rep2._differentials.keys(): 89 return False 90 for key, diff1 in rep1._differentials.items(): 91 result &= components_allclose(diff1, rep2._differentials[key]) 92 elif getattr(rep2, '_differentials', False): 93 return False 94 95 return result & components_allclose(rep1, rep2) 96 97 98class TestRadialRepresentation: 99 100 def test_transform(self): 101 """Test the ``transform`` method. Only multiplication matrices pass.""" 102 rep = RadialRepresentation(distance=10 * u.kpc) 103 104 # a rotation matrix does not work 105 matrix = rotation_matrix(10 * u.deg) 106 with pytest.raises(ValueError, match="scaled identity matrix"): 107 rep.transform(matrix) 108 109 # only a scaled identity matrix 110 matrix = 3 * np.identity(3) 111 newrep = rep.transform(matrix) 112 assert newrep.distance == 30 * u.kpc 113 114 # let's also check with differentials 115 dif = RadialDifferential(d_distance=-3 * u.km / u.s) 116 rep = rep.with_differentials(dict(s=dif)) 117 118 newrep = rep.transform(matrix) 119 assert newrep.distance == 30 * u.kpc 120 assert newrep.differentials["s"].d_distance == -9 * u.km / u.s 121 122 123class TestSphericalRepresentation: 124 125 def test_name(self): 126 assert SphericalRepresentation.get_name() == 'spherical' 127 assert SphericalRepresentation.get_name() in REPRESENTATION_CLASSES 128 129 def test_empty_init(self): 130 with pytest.raises(TypeError) as exc: 131 s = SphericalRepresentation() 132 133 def test_init_quantity(self): 134 135 s3 = SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) 136 assert s3.lon == 8. * u.hourangle 137 assert s3.lat == 5. * u.deg 138 assert s3.distance == 10 * u.kpc 139 140 assert isinstance(s3.lon, Longitude) 141 assert isinstance(s3.lat, Latitude) 142 assert isinstance(s3.distance, Distance) 143 144 def test_init_no_mutate_input(self): 145 146 lon = -1 * u.hourangle 147 s = SphericalRepresentation(lon=lon, lat=-1 * u.deg, distance=1 * u.kpc, copy=True) 148 149 # The longitude component should be wrapped at 24 hours 150 assert_allclose_quantity(s.lon, 23 * u.hourangle) 151 152 # The input should not have been mutated by the constructor 153 assert_allclose_quantity(lon, -1 * u.hourangle) 154 155 def test_init_lonlat(self): 156 157 s2 = SphericalRepresentation(Longitude(8, u.hour), 158 Latitude(5, u.deg), 159 Distance(10, u.kpc)) 160 161 assert s2.lon == 8. * u.hourangle 162 assert s2.lat == 5. * u.deg 163 assert s2.distance == 10. * u.kpc 164 165 assert isinstance(s2.lon, Longitude) 166 assert isinstance(s2.lat, Latitude) 167 assert isinstance(s2.distance, Distance) 168 169 # also test that wrap_angle is preserved 170 s3 = SphericalRepresentation(Longitude(-90, u.degree, 171 wrap_angle=180*u.degree), 172 Latitude(-45, u.degree), 173 Distance(1., u.Rsun)) 174 assert s3.lon == -90. * u.degree 175 assert s3.lon.wrap_angle == 180 * u.degree 176 177 def test_init_subclass(self): 178 class Longitude180(Longitude): 179 _default_wrap_angle = 180*u.degree 180 181 s = SphericalRepresentation(Longitude180(-90, u.degree), 182 Latitude(-45, u.degree), 183 Distance(1., u.Rsun)) 184 assert isinstance(s.lon, Longitude180) 185 assert s.lon == -90. * u.degree 186 assert s.lon.wrap_angle == 180 * u.degree 187 188 def test_init_array(self): 189 190 s1 = SphericalRepresentation(lon=[8, 9] * u.hourangle, 191 lat=[5, 6] * u.deg, 192 distance=[1, 2] * u.kpc) 193 194 assert_allclose(s1.lon.degree, [120, 135]) 195 assert_allclose(s1.lat.degree, [5, 6]) 196 assert_allclose(s1.distance.kpc, [1, 2]) 197 198 assert isinstance(s1.lon, Longitude) 199 assert isinstance(s1.lat, Latitude) 200 assert isinstance(s1.distance, Distance) 201 202 def test_init_array_nocopy(self): 203 204 lon = Longitude([8, 9] * u.hourangle) 205 lat = Latitude([5, 6] * u.deg) 206 distance = Distance([1, 2] * u.kpc) 207 208 s1 = SphericalRepresentation(lon=lon, lat=lat, distance=distance, copy=False) 209 210 lon[:] = [1, 2] * u.rad 211 lat[:] = [3, 4] * u.arcmin 212 distance[:] = [8, 9] * u.Mpc 213 214 assert_allclose_quantity(lon, s1.lon) 215 assert_allclose_quantity(lat, s1.lat) 216 assert_allclose_quantity(distance, s1.distance) 217 218 def test_init_float32_array(self): 219 """Regression test against #2983""" 220 lon = Longitude(np.float32([1., 2.]), u.degree) 221 lat = Latitude(np.float32([3., 4.]), u.degree) 222 s1 = UnitSphericalRepresentation(lon=lon, lat=lat, copy=False) 223 assert s1.lon.dtype == np.float32 224 assert s1.lat.dtype == np.float32 225 assert s1._values['lon'].dtype == np.float32 226 assert s1._values['lat'].dtype == np.float32 227 228 def test_reprobj(self): 229 230 s1 = SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) 231 232 s2 = SphericalRepresentation.from_representation(s1) 233 234 assert_allclose_quantity(s2.lon, 8. * u.hourangle) 235 assert_allclose_quantity(s2.lat, 5. * u.deg) 236 assert_allclose_quantity(s2.distance, 10 * u.kpc) 237 238 s3 = SphericalRepresentation(s1) 239 240 assert representation_equal(s1, s3) 241 242 def test_broadcasting(self): 243 244 s1 = SphericalRepresentation(lon=[8, 9] * u.hourangle, 245 lat=[5, 6] * u.deg, 246 distance=10 * u.kpc) 247 248 assert_allclose_quantity(s1.lon, [120, 135] * u.degree) 249 assert_allclose_quantity(s1.lat, [5, 6] * u.degree) 250 assert_allclose_quantity(s1.distance, [10, 10] * u.kpc) 251 252 def test_broadcasting_mismatch(self): 253 254 with pytest.raises(ValueError) as exc: 255 s1 = SphericalRepresentation(lon=[8, 9, 10] * u.hourangle, 256 lat=[5, 6] * u.deg, 257 distance=[1, 2] * u.kpc) 258 assert exc.value.args[0] == "Input parameters lon, lat, and distance cannot be broadcast" 259 260 def test_readonly(self): 261 262 s1 = SphericalRepresentation(lon=8 * u.hourangle, 263 lat=5 * u.deg, 264 distance=1. * u.kpc) 265 266 with pytest.raises(AttributeError): 267 s1.lon = 1. * u.deg 268 269 with pytest.raises(AttributeError): 270 s1.lat = 1. * u.deg 271 272 with pytest.raises(AttributeError): 273 s1.distance = 1. * u.kpc 274 275 def test_getitem_len_iterable(self): 276 277 s = SphericalRepresentation(lon=np.arange(10) * u.deg, 278 lat=-np.arange(10) * u.deg, 279 distance=1 * u.kpc) 280 281 s_slc = s[2:8:2] 282 283 assert_allclose_quantity(s_slc.lon, [2, 4, 6] * u.deg) 284 assert_allclose_quantity(s_slc.lat, [-2, -4, -6] * u.deg) 285 assert_allclose_quantity(s_slc.distance, [1, 1, 1] * u.kpc) 286 287 assert len(s) == 10 288 assert isiterable(s) 289 290 def test_getitem_len_iterable_scalar(self): 291 292 s = SphericalRepresentation(lon=1 * u.deg, 293 lat=-2 * u.deg, 294 distance=3 * u.kpc) 295 296 with pytest.raises(TypeError): 297 s_slc = s[0] 298 with pytest.raises(TypeError): 299 len(s) 300 assert not isiterable(s) 301 302 def test_setitem(self): 303 s = SphericalRepresentation(lon=np.arange(5) * u.deg, 304 lat=-np.arange(5) * u.deg, 305 distance=1 * u.kpc) 306 s[:2] = SphericalRepresentation(lon=10.*u.deg, lat=2.*u.deg, 307 distance=5.*u.kpc) 308 assert_allclose_quantity(s.lon, [10, 10, 2, 3, 4] * u.deg) 309 assert_allclose_quantity(s.lat, [2, 2, -2, -3, -4] * u.deg) 310 assert_allclose_quantity(s.distance, [5, 5, 1, 1, 1] * u.kpc) 311 312 def test_negative_distance(self): 313 """Only allowed if explicitly passed on.""" 314 with pytest.raises(ValueError, match='allow_negative'): 315 SphericalRepresentation(10*u.deg, 20*u.deg, -10*u.m) 316 317 s1 = SphericalRepresentation(10*u.deg, 20*u.deg, 318 Distance(-10*u.m, allow_negative=True)) 319 320 assert s1.distance == -10.*u.m 321 322 def test_nan_distance(self): 323 """ This is a regression test: calling represent_as() and passing in the 324 same class as the object shouldn't round-trip through cartesian. 325 """ 326 327 sph = SphericalRepresentation(1*u.deg, 2*u.deg, np.nan*u.kpc) 328 new_sph = sph.represent_as(SphericalRepresentation) 329 assert_allclose_quantity(new_sph.lon, sph.lon) 330 assert_allclose_quantity(new_sph.lat, sph.lat) 331 332 dif = SphericalCosLatDifferential(1*u.mas/u.yr, 2*u.mas/u.yr, 333 3*u.km/u.s) 334 sph = sph.with_differentials(dif) 335 new_sph = sph.represent_as(SphericalRepresentation) 336 assert_allclose_quantity(new_sph.lon, sph.lon) 337 assert_allclose_quantity(new_sph.lat, sph.lat) 338 339 def test_raise_on_extra_arguments(self): 340 with pytest.raises(TypeError, match='got multiple values'): 341 SphericalRepresentation(1*u.deg, 2*u.deg, 1.*u.kpc, lat=10) 342 343 with pytest.raises(TypeError, match='unexpected keyword.*parrot'): 344 SphericalRepresentation(1*u.deg, 2*u.deg, 1.*u.kpc, parrot=10) 345 346 def test_representation_shortcuts(self): 347 """Test that shortcuts in ``represent_as`` don't fail.""" 348 difs = SphericalCosLatDifferential(4*u.mas/u.yr,5*u.mas/u.yr,6*u.km/u.s) 349 sph = SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc, 350 differentials={'s': difs}) 351 352 got = sph.represent_as(PhysicsSphericalRepresentation, 353 PhysicsSphericalDifferential) 354 assert np.may_share_memory(sph.lon, got.phi) 355 assert np.may_share_memory(sph.distance, got.r) 356 expected = BaseRepresentation.represent_as( 357 sph, PhysicsSphericalRepresentation, PhysicsSphericalDifferential) 358 # equal up to angular type 359 assert representation_equal_up_to_angular_type(got, expected) 360 361 got = sph.represent_as(UnitSphericalRepresentation, 362 UnitSphericalDifferential) 363 assert np.may_share_memory(sph.lon, got.lon) 364 assert np.may_share_memory(sph.lat, got.lat) 365 expected = BaseRepresentation.represent_as( 366 sph, UnitSphericalRepresentation, UnitSphericalDifferential) 367 assert representation_equal_up_to_angular_type(got, expected) 368 369 def test_transform(self): 370 """Test ``.transform()`` on rotation and general matrices.""" 371 # set up representation 372 ds1 = SphericalDifferential( 373 d_lon=[1, 2] * u.mas / u.yr, d_lat=[3, 4] * u.mas / u.yr, 374 d_distance=[-5, 6] * u.km / u.s) 375 s1 = SphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, 376 distance=[5, 6] * u.kpc, differentials=ds1) 377 378 # transform representation & get comparison (thru CartesianRep) 379 s2 = s1.transform(matrices["rotation"]) 380 ds2 = s2.differentials["s"] 381 382 dexpected = SphericalDifferential.from_cartesian( 383 ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) 384 385 assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) 386 assert_allclose_quantity(s2.lat, s1.lat) 387 assert_allclose_quantity(s2.distance, s1.distance) 388 # check differentials. they shouldn't have changed. 389 assert_allclose_quantity(ds2.d_lon, ds1.d_lon) 390 assert_allclose_quantity(ds2.d_lat, ds1.d_lat) 391 assert_allclose_quantity(ds2.d_distance, ds1.d_distance) 392 assert_allclose_quantity(ds2.d_lon, dexpected.d_lon) 393 assert_allclose_quantity(ds2.d_lat, dexpected.d_lat) 394 assert_allclose_quantity(ds2.d_distance, dexpected.d_distance) 395 396 # now with a non rotation matrix 397 # transform representation & get comparison (thru CartesianRep) 398 s3 = s1.transform(matrices["general"]) 399 ds3 = s3.differentials["s"] 400 401 expected = (s1.represent_as(CartesianRepresentation, 402 CartesianDifferential) 403 .transform(matrices["general"]) 404 .represent_as(SphericalRepresentation, 405 SphericalDifferential)) 406 dexpected = expected.differentials["s"] 407 408 assert_allclose_quantity(s3.lon, expected.lon) 409 assert_allclose_quantity(s3.lat, expected.lat) 410 assert_allclose_quantity(s3.distance, expected.distance) 411 assert_allclose_quantity(ds3.d_lon, dexpected.d_lon) 412 assert_allclose_quantity(ds3.d_lat, dexpected.d_lat) 413 assert_allclose_quantity(ds3.d_distance, dexpected.d_distance) 414 415 def test_transform_with_NaN(self): 416 # all over again, but with a NaN in the distance 417 418 ds1 = SphericalDifferential( 419 d_lon=[1, 2] * u.mas / u.yr, d_lat=[3, 4] * u.mas / u.yr, 420 d_distance=[-5, 6] * u.km / u.s) 421 s1 = SphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, 422 distance=[5, np.nan] * u.kpc, 423 differentials=ds1) 424 425 # transform representation & get comparison (thru CartesianRep) 426 s2 = s1.transform(matrices["rotation"]) 427 ds2 = s2.differentials["s"] 428 429 dexpected = SphericalDifferential.from_cartesian( 430 ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) 431 432 assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) 433 assert_allclose_quantity(s2.lat, s1.lat) 434 assert_allclose_quantity(s2.distance, s1.distance) 435 assert_allclose_quantity(ds2.d_lon, dexpected.d_lon) 436 assert_allclose_quantity(ds2.d_lat, dexpected.d_lat) 437 assert_allclose_quantity(ds2.d_distance, dexpected.d_distance) 438 # the 2nd component is NaN since the 2nd distance is NaN 439 # TODO! this will change when ``.transform`` skips Cartesian 440 assert_array_equal(np.isnan(ds2.d_lon), (False, True)) 441 assert_array_equal(np.isnan(ds2.d_lat), (False, True)) 442 assert_array_equal(np.isnan(ds2.d_distance), (False, True)) 443 444 # now with a non rotation matrix 445 s3 = s1.transform(matrices["general"]) 446 ds3 = s3.differentials["s"] 447 448 thruC = (s1.represent_as(CartesianRepresentation, 449 CartesianDifferential) 450 .transform(matrices["general"]) 451 .represent_as(SphericalRepresentation, 452 differential_class=SphericalDifferential)) 453 dthruC = thruC.differentials["s"] 454 455 # s3 should not propagate Nan. 456 assert_array_equal(np.isnan(s3.lon), (False, False)) 457 assert_array_equal(np.isnan(s3.lat), (False, False)) 458 assert_array_equal(np.isnan(s3.distance), (False, True)) 459 # ds3 does b/c currently aren't any shortcuts on the transform 460 assert_array_equal(np.isnan(ds3.d_lon), (False, True)) 461 assert_array_equal(np.isnan(ds3.d_lat), (False, True)) 462 assert_array_equal(np.isnan(ds3.d_distance), (False, True)) 463 464 # through Cartesian should 465 assert_array_equal(np.isnan(thruC.lon), (False, True)) 466 assert_array_equal(np.isnan(thruC.lat), (False, True)) 467 assert_array_equal(np.isnan(thruC.distance), (False, True)) 468 assert_array_equal(np.isnan(dthruC.d_lon), (False, True)) 469 assert_array_equal(np.isnan(dthruC.d_lat), (False, True)) 470 assert_array_equal(np.isnan(dthruC.d_distance), (False, True)) 471 # test that they are close on the first value 472 assert_allclose_quantity(s3.lon[0], thruC.lon[0]) 473 assert_allclose_quantity(s3.lat[0], thruC.lat[0]) 474 assert_allclose_quantity(ds3.d_lon[0], dthruC.d_lon[0]) 475 assert_allclose_quantity(ds3.d_lat[0], dthruC.d_lat[0]) 476 477 478class TestUnitSphericalRepresentation: 479 480 def test_name(self): 481 assert UnitSphericalRepresentation.get_name() == 'unitspherical' 482 assert UnitSphericalRepresentation.get_name() in REPRESENTATION_CLASSES 483 484 def test_empty_init(self): 485 with pytest.raises(TypeError) as exc: 486 s = UnitSphericalRepresentation() 487 488 def test_init_quantity(self): 489 490 s3 = UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) 491 assert s3.lon == 8. * u.hourangle 492 assert s3.lat == 5. * u.deg 493 494 assert isinstance(s3.lon, Longitude) 495 assert isinstance(s3.lat, Latitude) 496 497 def test_init_lonlat(self): 498 499 s2 = UnitSphericalRepresentation(Longitude(8, u.hour), 500 Latitude(5, u.deg)) 501 502 assert s2.lon == 8. * u.hourangle 503 assert s2.lat == 5. * u.deg 504 505 assert isinstance(s2.lon, Longitude) 506 assert isinstance(s2.lat, Latitude) 507 508 def test_init_array(self): 509 510 s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, 511 lat=[5, 6] * u.deg) 512 513 assert_allclose(s1.lon.degree, [120, 135]) 514 assert_allclose(s1.lat.degree, [5, 6]) 515 516 assert isinstance(s1.lon, Longitude) 517 assert isinstance(s1.lat, Latitude) 518 519 def test_init_array_nocopy(self): 520 521 lon = Longitude([8, 9] * u.hourangle) 522 lat = Latitude([5, 6] * u.deg) 523 524 s1 = UnitSphericalRepresentation(lon=lon, lat=lat, copy=False) 525 526 lon[:] = [1, 2] * u.rad 527 lat[:] = [3, 4] * u.arcmin 528 529 assert_allclose_quantity(lon, s1.lon) 530 assert_allclose_quantity(lat, s1.lat) 531 532 def test_reprobj(self): 533 534 s1 = UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) 535 536 s2 = UnitSphericalRepresentation.from_representation(s1) 537 538 assert_allclose_quantity(s2.lon, 8. * u.hourangle) 539 assert_allclose_quantity(s2.lat, 5. * u.deg) 540 541 s3 = UnitSphericalRepresentation(s1) 542 543 assert representation_equal(s3, s1) 544 545 def test_broadcasting(self): 546 547 s1 = UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, 548 lat=[5, 6] * u.deg) 549 550 assert_allclose_quantity(s1.lon, [120, 135] * u.degree) 551 assert_allclose_quantity(s1.lat, [5, 6] * u.degree) 552 553 def test_broadcasting_mismatch(self): 554 555 with pytest.raises(ValueError) as exc: 556 s1 = UnitSphericalRepresentation(lon=[8, 9, 10] * u.hourangle, 557 lat=[5, 6] * u.deg) 558 assert exc.value.args[0] == "Input parameters lon and lat cannot be broadcast" 559 560 def test_readonly(self): 561 562 s1 = UnitSphericalRepresentation(lon=8 * u.hourangle, 563 lat=5 * u.deg) 564 565 with pytest.raises(AttributeError): 566 s1.lon = 1. * u.deg 567 568 with pytest.raises(AttributeError): 569 s1.lat = 1. * u.deg 570 571 def test_getitem(self): 572 573 s = UnitSphericalRepresentation(lon=np.arange(10) * u.deg, 574 lat=-np.arange(10) * u.deg) 575 576 s_slc = s[2:8:2] 577 578 assert_allclose_quantity(s_slc.lon, [2, 4, 6] * u.deg) 579 assert_allclose_quantity(s_slc.lat, [-2, -4, -6] * u.deg) 580 581 def test_getitem_scalar(self): 582 583 s = UnitSphericalRepresentation(lon=1 * u.deg, 584 lat=-2 * u.deg) 585 586 with pytest.raises(TypeError): 587 s_slc = s[0] 588 589 def test_representation_shortcuts(self): 590 """Test that shortcuts in ``represent_as`` don't fail.""" 591 # TODO! representation transformations with differentials cannot 592 # (currently) be implemented due to a mismatch between the UnitSpherical 593 # expected keys (e.g. "s") and that expected in the other class 594 # (here "s / m"). For more info, see PR #11467 595 # We leave the test code commented out for future use. 596 # diffs = UnitSphericalCosLatDifferential(4*u.mas/u.yr, 5*u.mas/u.yr, 597 # 6*u.km/u.s) 598 sph = UnitSphericalRepresentation(1*u.deg, 2*u.deg) 599 # , differentials={'s': diffs} 600 got = sph.represent_as(PhysicsSphericalRepresentation) 601 # , PhysicsSphericalDifferential) 602 assert np.may_share_memory(sph.lon, got.phi) 603 expected = BaseRepresentation.represent_as( 604 sph, PhysicsSphericalRepresentation) # PhysicsSphericalDifferential 605 assert representation_equal_up_to_angular_type(got, expected) 606 607 got = sph.represent_as(SphericalRepresentation) 608 # , SphericalDifferential) 609 assert np.may_share_memory(sph.lon, got.lon) 610 assert np.may_share_memory(sph.lat, got.lat) 611 expected = BaseRepresentation.represent_as( 612 sph, SphericalRepresentation) # , SphericalDifferential) 613 assert representation_equal_up_to_angular_type(got, expected) 614 615 def test_transform(self): 616 """Test ``.transform()`` on rotation and general matrices.""" 617 # set up representation 618 ds1 = UnitSphericalDifferential(d_lon=[1, 2] * u.mas / u.yr, 619 d_lat=[3, 4] * u.mas / u.yr,) 620 s1 = UnitSphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg, 621 differentials=ds1) 622 623 # transform representation & get comparison (thru CartesianRep) 624 s2 = s1.transform(matrices["rotation"]) 625 ds2 = s2.differentials["s"] 626 627 dexpected = UnitSphericalDifferential.from_cartesian( 628 ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) 629 630 assert_allclose_quantity(s2.lon, s1.lon + 10 * u.deg) 631 assert_allclose_quantity(s2.lat, s1.lat) 632 # compare differentials. they should be unchanged (ds1). 633 assert_allclose_quantity(ds2.d_lon, ds1.d_lon) 634 assert_allclose_quantity(ds2.d_lat, ds1.d_lat) 635 assert_allclose_quantity(ds2.d_lon, dexpected.d_lon) 636 assert_allclose_quantity(ds2.d_lat, dexpected.d_lat) 637 assert not hasattr(ds2, "d_distance") 638 639 # now with a non rotation matrix 640 # note that the result will be a Spherical, not UnitSpherical 641 s3 = s1.transform(matrices["general"]) 642 ds3 = s3.differentials["s"] 643 644 expected = (s1.represent_as(CartesianRepresentation, 645 CartesianDifferential) 646 .transform(matrices["general"]) 647 .represent_as(SphericalRepresentation, 648 differential_class=SphericalDifferential)) 649 dexpected = expected.differentials["s"] 650 651 assert_allclose_quantity(s3.lon, expected.lon) 652 assert_allclose_quantity(s3.lat, expected.lat) 653 assert_allclose_quantity(s3.distance, expected.distance) 654 assert_allclose_quantity(ds3.d_lon, dexpected.d_lon) 655 assert_allclose_quantity(ds3.d_lat, dexpected.d_lat) 656 assert_allclose_quantity(ds3.d_distance, dexpected.d_distance) 657 658 659class TestPhysicsSphericalRepresentation: 660 661 def test_name(self): 662 assert PhysicsSphericalRepresentation.get_name() == 'physicsspherical' 663 assert PhysicsSphericalRepresentation.get_name() in REPRESENTATION_CLASSES 664 665 def test_empty_init(self): 666 with pytest.raises(TypeError) as exc: 667 s = PhysicsSphericalRepresentation() 668 669 def test_init_quantity(self): 670 671 s3 = PhysicsSphericalRepresentation(phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc) 672 assert s3.phi == 8. * u.hourangle 673 assert s3.theta == 5. * u.deg 674 assert s3.r == 10 * u.kpc 675 676 assert isinstance(s3.phi, Angle) 677 assert isinstance(s3.theta, Angle) 678 assert isinstance(s3.r, Distance) 679 680 def test_init_phitheta(self): 681 682 s2 = PhysicsSphericalRepresentation(Angle(8, u.hour), 683 Angle(5, u.deg), 684 Distance(10, u.kpc)) 685 686 assert s2.phi == 8. * u.hourangle 687 assert s2.theta == 5. * u.deg 688 assert s2.r == 10. * u.kpc 689 690 assert isinstance(s2.phi, Angle) 691 assert isinstance(s2.theta, Angle) 692 assert isinstance(s2.r, Distance) 693 694 def test_init_array(self): 695 696 s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, 697 theta=[5, 6] * u.deg, 698 r=[1, 2] * u.kpc) 699 700 assert_allclose(s1.phi.degree, [120, 135]) 701 assert_allclose(s1.theta.degree, [5, 6]) 702 assert_allclose(s1.r.kpc, [1, 2]) 703 704 assert isinstance(s1.phi, Angle) 705 assert isinstance(s1.theta, Angle) 706 assert isinstance(s1.r, Distance) 707 708 def test_init_array_nocopy(self): 709 710 phi = Angle([8, 9] * u.hourangle) 711 theta = Angle([5, 6] * u.deg) 712 r = Distance([1, 2] * u.kpc) 713 714 s1 = PhysicsSphericalRepresentation(phi=phi, theta=theta, r=r, copy=False) 715 716 phi[:] = [1, 2] * u.rad 717 theta[:] = [3, 4] * u.arcmin 718 r[:] = [8, 9] * u.Mpc 719 720 assert_allclose_quantity(phi, s1.phi) 721 assert_allclose_quantity(theta, s1.theta) 722 assert_allclose_quantity(r, s1.r) 723 724 def test_reprobj(self): 725 726 s1 = PhysicsSphericalRepresentation(phi=8 * u.hourangle, theta=5 * u.deg, r=10 * u.kpc) 727 728 s2 = PhysicsSphericalRepresentation.from_representation(s1) 729 730 assert_allclose_quantity(s2.phi, 8. * u.hourangle) 731 assert_allclose_quantity(s2.theta, 5. * u.deg) 732 assert_allclose_quantity(s2.r, 10 * u.kpc) 733 734 s3 = PhysicsSphericalRepresentation(s1) 735 736 assert representation_equal(s3, s1) 737 738 def test_broadcasting(self): 739 740 s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, 741 theta=[5, 6] * u.deg, 742 r=10 * u.kpc) 743 744 assert_allclose_quantity(s1.phi, [120, 135] * u.degree) 745 assert_allclose_quantity(s1.theta, [5, 6] * u.degree) 746 assert_allclose_quantity(s1.r, [10, 10] * u.kpc) 747 748 def test_broadcasting_mismatch(self): 749 750 with pytest.raises(ValueError) as exc: 751 s1 = PhysicsSphericalRepresentation(phi=[8, 9, 10] * u.hourangle, 752 theta=[5, 6] * u.deg, 753 r=[1, 2] * u.kpc) 754 assert exc.value.args[0] == "Input parameters phi, theta, and r cannot be broadcast" 755 756 def test_readonly(self): 757 758 s1 = PhysicsSphericalRepresentation(phi=[8, 9] * u.hourangle, 759 theta=[5, 6] * u.deg, 760 r=[10, 20] * u.kpc) 761 762 with pytest.raises(AttributeError): 763 s1.phi = 1. * u.deg 764 765 with pytest.raises(AttributeError): 766 s1.theta = 1. * u.deg 767 768 with pytest.raises(AttributeError): 769 s1.r = 1. * u.kpc 770 771 def test_getitem(self): 772 773 s = PhysicsSphericalRepresentation(phi=np.arange(10) * u.deg, 774 theta=np.arange(5, 15) * u.deg, 775 r=1 * u.kpc) 776 777 s_slc = s[2:8:2] 778 779 assert_allclose_quantity(s_slc.phi, [2, 4, 6] * u.deg) 780 assert_allclose_quantity(s_slc.theta, [7, 9, 11] * u.deg) 781 assert_allclose_quantity(s_slc.r, [1, 1, 1] * u.kpc) 782 783 def test_getitem_scalar(self): 784 785 s = PhysicsSphericalRepresentation(phi=1 * u.deg, 786 theta=2 * u.deg, 787 r=3 * u.kpc) 788 789 with pytest.raises(TypeError): 790 s_slc = s[0] 791 792 def test_representation_shortcuts(self): 793 """Test that shortcuts in ``represent_as`` don't fail.""" 794 difs = PhysicsSphericalDifferential(4*u.mas/u.yr,5*u.mas/u.yr,6*u.km/u.s) 795 sph = PhysicsSphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc, 796 differentials={'s': difs}) 797 798 got = sph.represent_as(SphericalRepresentation, 799 SphericalDifferential) 800 assert np.may_share_memory(sph.phi, got.lon) 801 assert np.may_share_memory(sph.r, got.distance) 802 expected = BaseRepresentation.represent_as( 803 sph, SphericalRepresentation, SphericalDifferential) 804 assert representation_equal_up_to_angular_type(got, expected) 805 806 got = sph.represent_as(UnitSphericalRepresentation, 807 UnitSphericalDifferential) 808 assert np.may_share_memory(sph.phi, got.lon) 809 expected = BaseRepresentation.represent_as( 810 sph, UnitSphericalRepresentation, UnitSphericalDifferential) 811 assert representation_equal_up_to_angular_type(got, expected) 812 813 def test_initialize_with_nan(self): 814 # Regression test for gh-11558: initialization used to fail. 815 psr = PhysicsSphericalRepresentation([1., np.nan]*u.deg, [np.nan, 2.]*u.deg, 816 [3., np.nan]*u.m) 817 assert_array_equal(np.isnan(psr.phi), [False, True]) 818 assert_array_equal(np.isnan(psr.theta), [True, False]) 819 assert_array_equal(np.isnan(psr.r), [False, True]) 820 821 def test_transform(self): 822 """Test ``.transform()`` on rotation and general transform matrices.""" 823 # set up representation 824 ds1 = PhysicsSphericalDifferential( 825 d_phi=[1, 2] * u.mas / u.yr, d_theta=[3, 4] * u.mas / u.yr, 826 d_r=[-5, 6] * u.km / u.s) 827 s1 = PhysicsSphericalRepresentation( 828 phi=[1, 2] * u.deg, theta=[3, 4] * u.deg, r=[5, 6] * u.kpc, 829 differentials=ds1) 830 831 # transform representation & get comparison (thru CartesianRep) 832 s2 = s1.transform(matrices["rotation"]) 833 ds2 = s2.differentials["s"] 834 835 dexpected = PhysicsSphericalDifferential.from_cartesian( 836 ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) 837 838 assert_allclose_quantity(s2.phi, s1.phi + 10 * u.deg) 839 assert_allclose_quantity(s2.theta, s1.theta) 840 assert_allclose_quantity(s2.r, s1.r) 841 # compare differentials. should be unchanged (ds1). 842 assert_allclose_quantity(ds2.d_phi, ds1.d_phi) 843 assert_allclose_quantity(ds2.d_theta, ds1.d_theta) 844 assert_allclose_quantity(ds2.d_r, ds1.d_r) 845 assert_allclose_quantity(ds2.d_phi, dexpected.d_phi) 846 assert_allclose_quantity(ds2.d_theta, dexpected.d_theta) 847 assert_allclose_quantity(ds2.d_r, dexpected.d_r) 848 849 # now with a non rotation matrix 850 # transform representation & get comparison (thru CartesianRep) 851 s3 = s1.transform(matrices["general"]) 852 ds3 = s3.differentials["s"] 853 854 expected = (s1.represent_as(CartesianRepresentation, 855 CartesianDifferential) 856 .transform(matrices["general"]) 857 .represent_as(PhysicsSphericalRepresentation, 858 PhysicsSphericalDifferential)) 859 dexpected = expected.differentials["s"] 860 861 assert_allclose_quantity(s3.phi, expected.phi) 862 assert_allclose_quantity(s3.theta, expected.theta) 863 assert_allclose_quantity(s3.r, expected.r) 864 assert_allclose_quantity(ds3.d_phi, dexpected.d_phi) 865 assert_allclose_quantity(ds3.d_theta, dexpected.d_theta) 866 assert_allclose_quantity(ds3.d_r, dexpected.d_r) 867 868 def test_transform_with_NaN(self): 869 # all over again, but with a NaN in the distance 870 871 ds1 = PhysicsSphericalDifferential( 872 d_phi=[1, 2] * u.mas / u.yr, d_theta=[3, 4] * u.mas / u.yr, 873 d_r=[-5, 6] * u.km / u.s) 874 s1 = PhysicsSphericalRepresentation( 875 phi=[1, 2] * u.deg, theta=[3, 4] * u.deg, r=[5, np.nan] * u.kpc, 876 differentials=ds1) 877 878 # transform representation & get comparison (thru CartesianRep) 879 s2 = s1.transform(matrices["rotation"]) 880 ds2 = s2.differentials["s"] 881 882 dexpected = PhysicsSphericalDifferential.from_cartesian( 883 ds1.to_cartesian(base=s1).transform(matrices["rotation"]), base=s2) 884 885 assert_allclose_quantity(s2.phi, s1.phi + 10 * u.deg) 886 assert_allclose_quantity(s2.theta, s1.theta) 887 assert_allclose_quantity(s2.r, s1.r) 888 assert_allclose_quantity(ds2.d_phi, dexpected.d_phi) 889 assert_allclose_quantity(ds2.d_theta, dexpected.d_theta) 890 assert_allclose_quantity(ds2.d_r, dexpected.d_r) 891 892 # now with a non rotation matrix 893 s3 = s1.transform(matrices["general"]) 894 ds3 = s3.differentials["s"] 895 896 thruC = (s1.represent_as(CartesianRepresentation, 897 CartesianDifferential) 898 .transform(matrices["general"]) 899 .represent_as(PhysicsSphericalRepresentation, 900 PhysicsSphericalDifferential)) 901 dthruC = thruC.differentials["s"] 902 903 # s3 should not propagate Nan. 904 assert_array_equal(np.isnan(s3.phi), (False, False)) 905 assert_array_equal(np.isnan(s3.theta), (False, False)) 906 assert_array_equal(np.isnan(s3.r), (False, True)) 907 # ds3 does b/c currently aren't any shortcuts on the transform 908 assert_array_equal(np.isnan(ds3.d_phi), (False, True)) 909 assert_array_equal(np.isnan(ds3.d_theta), (False, True)) 910 assert_array_equal(np.isnan(ds3.d_r), (False, True)) 911 912 # through Cartesian does 913 assert_array_equal(np.isnan(thruC.phi), (False, True)) 914 assert_array_equal(np.isnan(thruC.theta), (False, True)) 915 assert_array_equal(np.isnan(thruC.r), (False, True)) 916 # so only test on the first value 917 assert_allclose_quantity(s3.phi[0], thruC.phi[0]) 918 assert_allclose_quantity(s3.theta[0], thruC.theta[0]) 919 assert_allclose_quantity(ds3.d_phi[0], dthruC.d_phi[0]) 920 assert_allclose_quantity(ds3.d_theta[0], dthruC.d_theta[0]) 921 922 923class TestCartesianRepresentation: 924 925 def test_name(self): 926 assert CartesianRepresentation.get_name() == 'cartesian' 927 assert CartesianRepresentation.get_name() in REPRESENTATION_CLASSES 928 929 def test_empty_init(self): 930 with pytest.raises(TypeError) as exc: 931 s = CartesianRepresentation() 932 933 def test_init_quantity(self): 934 935 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 936 937 assert s1.x.unit is u.kpc 938 assert s1.y.unit is u.kpc 939 assert s1.z.unit is u.kpc 940 941 assert_allclose(s1.x.value, 1) 942 assert_allclose(s1.y.value, 2) 943 assert_allclose(s1.z.value, 3) 944 945 def test_init_singleunit(self): 946 947 s1 = CartesianRepresentation(x=1, y=2, z=3, unit=u.kpc) 948 949 assert s1.x.unit is u.kpc 950 assert s1.y.unit is u.kpc 951 assert s1.z.unit is u.kpc 952 953 assert_allclose(s1.x.value, 1) 954 assert_allclose(s1.y.value, 2) 955 assert_allclose(s1.z.value, 3) 956 957 def test_init_array(self): 958 959 s1 = CartesianRepresentation(x=[1, 2, 3] * u.pc, 960 y=[2, 3, 4] * u.Mpc, 961 z=[3, 4, 5] * u.kpc) 962 963 assert s1.x.unit is u.pc 964 assert s1.y.unit is u.Mpc 965 assert s1.z.unit is u.kpc 966 967 assert_allclose(s1.x.value, [1, 2, 3]) 968 assert_allclose(s1.y.value, [2, 3, 4]) 969 assert_allclose(s1.z.value, [3, 4, 5]) 970 971 def test_init_one_array(self): 972 973 s1 = CartesianRepresentation(x=[1, 2, 3] * u.pc) 974 975 assert s1.x.unit is u.pc 976 assert s1.y.unit is u.pc 977 assert s1.z.unit is u.pc 978 979 assert_allclose(s1.x.value, 1) 980 assert_allclose(s1.y.value, 2) 981 assert_allclose(s1.z.value, 3) 982 983 r = np.arange(27.).reshape(3, 3, 3) * u.kpc 984 s2 = CartesianRepresentation(r, xyz_axis=0) 985 assert s2.shape == (3, 3) 986 assert s2.x.unit == u.kpc 987 assert np.all(s2.x == r[0]) 988 assert np.all(s2.xyz == r) 989 assert np.all(s2.get_xyz(xyz_axis=0) == r) 990 s3 = CartesianRepresentation(r, xyz_axis=1) 991 assert s3.shape == (3, 3) 992 assert np.all(s3.x == r[:, 0]) 993 assert np.all(s3.y == r[:, 1]) 994 assert np.all(s3.z == r[:, 2]) 995 assert np.all(s3.get_xyz(xyz_axis=1) == r) 996 s4 = CartesianRepresentation(r, xyz_axis=2) 997 assert s4.shape == (3, 3) 998 assert np.all(s4.x == r[:, :, 0]) 999 assert np.all(s4.get_xyz(xyz_axis=2) == r) 1000 s5 = CartesianRepresentation(r, unit=u.pc) 1001 assert s5.x.unit == u.pc 1002 assert np.all(s5.xyz == r) 1003 s6 = CartesianRepresentation(r.value, unit=u.pc, xyz_axis=2) 1004 assert s6.x.unit == u.pc 1005 assert np.all(s6.get_xyz(xyz_axis=2).value == r.value) 1006 1007 def test_init_one_array_size_fail(self): 1008 with pytest.raises(ValueError) as exc: 1009 CartesianRepresentation(x=[1, 2, 3, 4] * u.pc) 1010 assert exc.value.args[0].startswith("too many values to unpack") 1011 1012 def test_init_xyz_but_more_than_one_array_fail(self): 1013 with pytest.raises(ValueError) as exc: 1014 CartesianRepresentation(x=[1, 2, 3] * u.pc, y=[2, 3, 4] * u.pc, 1015 z=[3, 4, 5] * u.pc, xyz_axis=0) 1016 assert 'xyz_axis should only be set' in str(exc.value) 1017 1018 def test_init_one_array_yz_fail(self): 1019 with pytest.raises(ValueError) as exc: 1020 CartesianRepresentation(x=[1, 2, 3, 4] * u.pc, y=[1, 2] * u.pc) 1021 assert exc.value.args[0] == ("x, y, and z are required to instantiate " 1022 "CartesianRepresentation") 1023 1024 def test_init_array_nocopy(self): 1025 1026 x = [8, 9, 10] * u.pc 1027 y = [5, 6, 7] * u.Mpc 1028 z = [2, 3, 4] * u.kpc 1029 1030 s1 = CartesianRepresentation(x=x, y=y, z=z, copy=False) 1031 1032 x[:] = [1, 2, 3] * u.kpc 1033 y[:] = [9, 9, 8] * u.kpc 1034 z[:] = [1, 2, 1] * u.kpc 1035 1036 assert_allclose_quantity(x, s1.x) 1037 assert_allclose_quantity(y, s1.y) 1038 assert_allclose_quantity(z, s1.z) 1039 1040 def test_xyz_is_view_if_possible(self): 1041 xyz = np.arange(1., 10.).reshape(3, 3) 1042 s1 = CartesianRepresentation(xyz, unit=u.kpc, copy=False) 1043 s1_xyz = s1.xyz 1044 assert s1_xyz.value[0, 0] == 1. 1045 xyz[0, 0] = 0. 1046 assert s1.x[0] == 0. 1047 assert s1_xyz.value[0, 0] == 0. 1048 # Not possible: we don't check that tuples are from the same array 1049 xyz = np.arange(1., 10.).reshape(3, 3) 1050 s2 = CartesianRepresentation(*xyz, unit=u.kpc, copy=False) 1051 s2_xyz = s2.xyz 1052 assert s2_xyz.value[0, 0] == 1. 1053 xyz[0, 0] = 0. 1054 assert s2.x[0] == 0. 1055 assert s2_xyz.value[0, 0] == 1. 1056 1057 def test_reprobj(self): 1058 1059 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1060 1061 s2 = CartesianRepresentation.from_representation(s1) 1062 1063 assert s2.x == 1 * u.kpc 1064 assert s2.y == 2 * u.kpc 1065 assert s2.z == 3 * u.kpc 1066 1067 s3 = CartesianRepresentation(s1) 1068 1069 assert representation_equal(s3, s1) 1070 1071 def test_broadcasting(self): 1072 1073 s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=5 * u.kpc) 1074 1075 assert s1.x.unit == u.kpc 1076 assert s1.y.unit == u.kpc 1077 assert s1.z.unit == u.kpc 1078 1079 assert_allclose(s1.x.value, [1, 2]) 1080 assert_allclose(s1.y.value, [3, 4]) 1081 assert_allclose(s1.z.value, [5, 5]) 1082 1083 def test_broadcasting_mismatch(self): 1084 1085 with pytest.raises(ValueError) as exc: 1086 s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, z=[5, 6, 7] * u.kpc) 1087 assert exc.value.args[0] == "Input parameters x, y, and z cannot be broadcast" 1088 1089 def test_readonly(self): 1090 1091 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1092 1093 with pytest.raises(AttributeError): 1094 s1.x = 1. * u.kpc 1095 1096 with pytest.raises(AttributeError): 1097 s1.y = 1. * u.kpc 1098 1099 with pytest.raises(AttributeError): 1100 s1.z = 1. * u.kpc 1101 1102 def test_xyz(self): 1103 1104 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1105 1106 assert isinstance(s1.xyz, u.Quantity) 1107 assert s1.xyz.unit is u.kpc 1108 1109 assert_allclose(s1.xyz.value, [1, 2, 3]) 1110 1111 def test_unit_mismatch(self): 1112 1113 q_len = u.Quantity([1], u.km) 1114 q_nonlen = u.Quantity([1], u.kg) 1115 1116 with pytest.raises(u.UnitsError) as exc: 1117 s1 = CartesianRepresentation(x=q_nonlen, y=q_len, z=q_len) 1118 assert exc.value.args[0] == "x, y, and z should have matching physical types" 1119 1120 with pytest.raises(u.UnitsError) as exc: 1121 s1 = CartesianRepresentation(x=q_len, y=q_nonlen, z=q_len) 1122 assert exc.value.args[0] == "x, y, and z should have matching physical types" 1123 1124 with pytest.raises(u.UnitsError) as exc: 1125 s1 = CartesianRepresentation(x=q_len, y=q_len, z=q_nonlen) 1126 assert exc.value.args[0] == "x, y, and z should have matching physical types" 1127 1128 def test_unit_non_length(self): 1129 1130 s1 = CartesianRepresentation(x=1 * u.kg, y=2 * u.kg, z=3 * u.kg) 1131 1132 s2 = CartesianRepresentation(x=1 * u.km / u.s, y=2 * u.km / u.s, z=3 * u.km / u.s) 1133 1134 banana = u.def_unit('banana') 1135 s3 = CartesianRepresentation(x=1 * banana, y=2 * banana, z=3 * banana) 1136 1137 def test_getitem(self): 1138 1139 s = CartesianRepresentation(x=np.arange(10) * u.m, 1140 y=-np.arange(10) * u.m, 1141 z=3 * u.km) 1142 1143 s_slc = s[2:8:2] 1144 1145 assert_allclose_quantity(s_slc.x, [2, 4, 6] * u.m) 1146 assert_allclose_quantity(s_slc.y, [-2, -4, -6] * u.m) 1147 assert_allclose_quantity(s_slc.z, [3, 3, 3] * u.km) 1148 1149 def test_getitem_scalar(self): 1150 1151 s = CartesianRepresentation(x=1 * u.m, 1152 y=-2 * u.m, 1153 z=3 * u.km) 1154 1155 with pytest.raises(TypeError): 1156 s_slc = s[0] 1157 1158 def test_transform(self): 1159 1160 ds1 = CartesianDifferential(d_x=[1, 2] * u.km / u.s, 1161 d_y=[3, 4] * u.km / u.s, 1162 d_z=[5, 6] * u.km / u.s) 1163 s1 = CartesianRepresentation(x=[1, 2] * u.kpc, y=[3, 4] * u.kpc, 1164 z=[5, 6] * u.kpc, differentials=ds1) 1165 1166 # transform representation & get comparison (thru CartesianRep) 1167 s2 = s1.transform(matrices["general"]) 1168 ds2 = s2.differentials["s"] 1169 1170 dexpected = CartesianDifferential.from_cartesian( 1171 ds1.to_cartesian(base=s1).transform(matrices["general"]), base=s2) 1172 1173 assert_allclose_quantity(ds2.d_x, dexpected.d_x) 1174 assert_allclose_quantity(ds2.d_y, dexpected.d_y) 1175 assert_allclose_quantity(ds2.d_z, dexpected.d_z) 1176 1177 # also explicitly calculate, since we can 1178 assert_allclose(s2.x.value, [1 * 1 + 2 * 3 + 3 * 5, 1 * 2 + 2 * 4 + 3 * 6]) 1179 assert_allclose(s2.y.value, [4 * 1 + 5 * 3 + 6 * 5, 4 * 2 + 5 * 4 + 6 * 6]) 1180 assert_allclose(s2.z.value, [7 * 1 + 8 * 3 + 9 * 5, 7 * 2 + 8 * 4 + 9 * 6]) 1181 assert_allclose(ds2.d_x.value, [1 * 1 + 2 * 3 + 3 * 5, 1 * 2 + 2 * 4 + 3 * 6]) 1182 assert_allclose(ds2.d_y.value, [4 * 1 + 5 * 3 + 6 * 5, 4 * 2 + 5 * 4 + 6 * 6]) 1183 assert_allclose(ds2.d_z.value, [7 * 1 + 8 * 3 + 9 * 5, 7 * 2 + 8 * 4 + 9 * 6]) 1184 1185 assert s2.x.unit is u.kpc 1186 assert s2.y.unit is u.kpc 1187 assert s2.z.unit is u.kpc 1188 assert ds2.d_x.unit == u.km / u.s 1189 assert ds2.d_y.unit == u.km / u.s 1190 assert ds2.d_z.unit == u.km / u.s 1191 1192 1193class TestCylindricalRepresentation: 1194 1195 def test_name(self): 1196 assert CylindricalRepresentation.get_name() == 'cylindrical' 1197 assert CylindricalRepresentation.get_name() in REPRESENTATION_CLASSES 1198 1199 def test_empty_init(self): 1200 with pytest.raises(TypeError) as exc: 1201 s = CylindricalRepresentation() 1202 1203 def test_init_quantity(self): 1204 1205 s1 = CylindricalRepresentation(rho=1 * u.kpc, phi=2 * u.deg, z=3 * u.kpc) 1206 1207 assert s1.rho.unit is u.kpc 1208 assert s1.phi.unit is u.deg 1209 assert s1.z.unit is u.kpc 1210 1211 assert_allclose(s1.rho.value, 1) 1212 assert_allclose(s1.phi.value, 2) 1213 assert_allclose(s1.z.value, 3) 1214 1215 def test_init_array(self): 1216 1217 s1 = CylindricalRepresentation(rho=[1, 2, 3] * u.pc, 1218 phi=[2, 3, 4] * u.deg, 1219 z=[3, 4, 5] * u.kpc) 1220 1221 assert s1.rho.unit is u.pc 1222 assert s1.phi.unit is u.deg 1223 assert s1.z.unit is u.kpc 1224 1225 assert_allclose(s1.rho.value, [1, 2, 3]) 1226 assert_allclose(s1.phi.value, [2, 3, 4]) 1227 assert_allclose(s1.z.value, [3, 4, 5]) 1228 1229 def test_init_array_nocopy(self): 1230 1231 rho = [8, 9, 10] * u.pc 1232 phi = [5, 6, 7] * u.deg 1233 z = [2, 3, 4] * u.kpc 1234 1235 s1 = CylindricalRepresentation(rho=rho, phi=phi, z=z, copy=False) 1236 1237 rho[:] = [9, 2, 3] * u.kpc 1238 phi[:] = [1, 2, 3] * u.arcmin 1239 z[:] = [-2, 3, 8] * u.kpc 1240 1241 assert_allclose_quantity(rho, s1.rho) 1242 assert_allclose_quantity(phi, s1.phi) 1243 assert_allclose_quantity(z, s1.z) 1244 1245 def test_reprobj(self): 1246 1247 s1 = CylindricalRepresentation(rho=1 * u.kpc, phi=2 * u.deg, z=3 * u.kpc) 1248 1249 s2 = CylindricalRepresentation.from_representation(s1) 1250 1251 assert s2.rho == 1 * u.kpc 1252 assert s2.phi == 2 * u.deg 1253 assert s2.z == 3 * u.kpc 1254 1255 s3 = CylindricalRepresentation(s1) 1256 1257 assert representation_equal(s3, s1) 1258 1259 def test_broadcasting(self): 1260 1261 s1 = CylindricalRepresentation(rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=5 * u.kpc) 1262 1263 assert s1.rho.unit == u.kpc 1264 assert s1.phi.unit == u.deg 1265 assert s1.z.unit == u.kpc 1266 1267 assert_allclose(s1.rho.value, [1, 2]) 1268 assert_allclose(s1.phi.value, [3, 4]) 1269 assert_allclose(s1.z.value, [5, 5]) 1270 1271 def test_broadcasting_mismatch(self): 1272 1273 with pytest.raises(ValueError) as exc: 1274 s1 = CylindricalRepresentation(rho=[1, 2] * u.kpc, phi=[3, 4] * u.deg, z=[5, 6, 7] * u.kpc) 1275 assert exc.value.args[0] == "Input parameters rho, phi, and z cannot be broadcast" 1276 1277 def test_readonly(self): 1278 1279 s1 = CylindricalRepresentation(rho=1 * u.kpc, 1280 phi=20 * u.deg, 1281 z=3 * u.kpc) 1282 1283 with pytest.raises(AttributeError): 1284 s1.rho = 1. * u.kpc 1285 1286 with pytest.raises(AttributeError): 1287 s1.phi = 20 * u.deg 1288 1289 with pytest.raises(AttributeError): 1290 s1.z = 1. * u.kpc 1291 1292 def unit_mismatch(self): 1293 1294 q_len = u.Quantity([1], u.kpc) 1295 q_nonlen = u.Quantity([1], u.kg) 1296 1297 with pytest.raises(u.UnitsError) as exc: 1298 s1 = CylindricalRepresentation(rho=q_nonlen, phi=10 * u.deg, z=q_len) 1299 assert exc.value.args[0] == "rho and z should have matching physical types" 1300 1301 with pytest.raises(u.UnitsError) as exc: 1302 s1 = CylindricalRepresentation(rho=q_len, phi=10 * u.deg, z=q_nonlen) 1303 assert exc.value.args[0] == "rho and z should have matching physical types" 1304 1305 def test_getitem(self): 1306 1307 s = CylindricalRepresentation(rho=np.arange(10) * u.pc, 1308 phi=-np.arange(10) * u.deg, 1309 z=1 * u.kpc) 1310 1311 s_slc = s[2:8:2] 1312 1313 assert_allclose_quantity(s_slc.rho, [2, 4, 6] * u.pc) 1314 assert_allclose_quantity(s_slc.phi, [-2, -4, -6] * u.deg) 1315 assert_allclose_quantity(s_slc.z, [1, 1, 1] * u.kpc) 1316 1317 def test_getitem_scalar(self): 1318 1319 s = CylindricalRepresentation(rho=1 * u.pc, 1320 phi=-2 * u.deg, 1321 z=3 * u.kpc) 1322 1323 with pytest.raises(TypeError): 1324 s_slc = s[0] 1325 1326 def test_transform(self): 1327 1328 s1 = CylindricalRepresentation(phi=[1, 2] * u.deg, z=[3, 4] * u.pc, 1329 rho=[5, 6] * u.kpc) 1330 1331 s2 = s1.transform(matrices["rotation"]) 1332 1333 assert_allclose_quantity(s2.phi, s1.phi + 10 * u.deg) 1334 assert_allclose_quantity(s2.z, s1.z) 1335 assert_allclose_quantity(s2.rho, s1.rho) 1336 1337 assert s2.phi.unit is u.rad 1338 assert s2.z.unit is u.kpc 1339 assert s2.rho.unit is u.kpc 1340 1341 # now with a non rotation matrix 1342 s3 = s1.transform(matrices["general"]) 1343 expected = (s1.to_cartesian().transform(matrices["general"]) 1344 ).represent_as(CylindricalRepresentation) 1345 1346 assert_allclose_quantity(s3.phi, expected.phi) 1347 assert_allclose_quantity(s3.z, expected.z) 1348 assert_allclose_quantity(s3.rho, expected.rho) 1349 1350 1351class TestUnitSphericalCosLatDifferential: 1352 1353 @pytest.mark.parametrize("matrix", list(matrices.values())) 1354 def test_transform(self, matrix): 1355 """Test ``.transform()`` on rotation and general matrices.""" 1356 # set up representation 1357 ds1 = UnitSphericalCosLatDifferential(d_lon_coslat=[1, 2] * u.mas / u.yr, 1358 d_lat=[3, 4] * u.mas / u.yr,) 1359 s1 = UnitSphericalRepresentation(lon=[1, 2] * u.deg, lat=[3, 4] * u.deg) 1360 1361 # transform representation & get comparison (thru CartesianRep) 1362 s2 = s1.transform(matrix) 1363 ds2 = ds1.transform(matrix, s1, s2) 1364 1365 dexpected = UnitSphericalCosLatDifferential.from_cartesian( 1366 ds1.to_cartesian(base=s1).transform(matrix), base=s2) 1367 1368 assert_allclose_quantity(ds2.d_lon_coslat, dexpected.d_lon_coslat) 1369 assert_allclose_quantity(ds2.d_lat, dexpected.d_lat) 1370 1371 1372def test_cartesian_spherical_roundtrip(): 1373 1374 s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, 1375 y=[3000., 4.] * u.pc, 1376 z=[5., 6000.] * u.pc) 1377 1378 s2 = SphericalRepresentation.from_representation(s1) 1379 1380 s3 = CartesianRepresentation.from_representation(s2) 1381 1382 s4 = SphericalRepresentation.from_representation(s3) 1383 1384 assert_allclose_quantity(s1.x, s3.x) 1385 assert_allclose_quantity(s1.y, s3.y) 1386 assert_allclose_quantity(s1.z, s3.z) 1387 1388 assert_allclose_quantity(s2.lon, s4.lon) 1389 assert_allclose_quantity(s2.lat, s4.lat) 1390 assert_allclose_quantity(s2.distance, s4.distance) 1391 1392 1393def test_cartesian_setting_with_other(): 1394 1395 s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, 1396 y=[3000., 4.] * u.pc, 1397 z=[5., 6000.] * u.pc) 1398 s1[0] = SphericalRepresentation(0.*u.deg, 0.*u.deg, 1*u.kpc) 1399 assert_allclose_quantity(s1.x, [1., 2000.] * u.kpc) 1400 assert_allclose_quantity(s1.y, [0., 4.] * u.pc) 1401 assert_allclose_quantity(s1.z, [0., 6000.] * u.pc) 1402 1403 with pytest.raises(ValueError, match='loss of information'): 1404 s1[1] = UnitSphericalRepresentation(0.*u.deg, 10.*u.deg) 1405 1406 1407def test_cartesian_physics_spherical_roundtrip(): 1408 1409 s1 = CartesianRepresentation(x=[1, 2000.] * u.kpc, 1410 y=[3000., 4.] * u.pc, 1411 z=[5., 6000.] * u.pc) 1412 1413 s2 = PhysicsSphericalRepresentation.from_representation(s1) 1414 1415 s3 = CartesianRepresentation.from_representation(s2) 1416 1417 s4 = PhysicsSphericalRepresentation.from_representation(s3) 1418 1419 assert_allclose_quantity(s1.x, s3.x) 1420 assert_allclose_quantity(s1.y, s3.y) 1421 assert_allclose_quantity(s1.z, s3.z) 1422 1423 assert_allclose_quantity(s2.phi, s4.phi) 1424 assert_allclose_quantity(s2.theta, s4.theta) 1425 assert_allclose_quantity(s2.r, s4.r) 1426 1427 1428def test_spherical_physics_spherical_roundtrip(): 1429 1430 s1 = SphericalRepresentation(lon=3 * u.deg, lat=4 * u.deg, distance=3 * u.kpc) 1431 1432 s2 = PhysicsSphericalRepresentation.from_representation(s1) 1433 1434 s3 = SphericalRepresentation.from_representation(s2) 1435 1436 s4 = PhysicsSphericalRepresentation.from_representation(s3) 1437 1438 assert_allclose_quantity(s1.lon, s3.lon) 1439 assert_allclose_quantity(s1.lat, s3.lat) 1440 assert_allclose_quantity(s1.distance, s3.distance) 1441 1442 assert_allclose_quantity(s2.phi, s4.phi) 1443 assert_allclose_quantity(s2.theta, s4.theta) 1444 assert_allclose_quantity(s2.r, s4.r) 1445 1446 assert_allclose_quantity(s1.lon, s4.phi) 1447 assert_allclose_quantity(s1.lat, 90. * u.deg - s4.theta) 1448 assert_allclose_quantity(s1.distance, s4.r) 1449 1450 1451def test_cartesian_cylindrical_roundtrip(): 1452 1453 s1 = CartesianRepresentation(x=np.array([1., 2000.]) * u.kpc, 1454 y=np.array([3000., 4.]) * u.pc, 1455 z=np.array([5., 600.]) * u.cm) 1456 1457 s2 = CylindricalRepresentation.from_representation(s1) 1458 1459 s3 = CartesianRepresentation.from_representation(s2) 1460 1461 s4 = CylindricalRepresentation.from_representation(s3) 1462 1463 assert_allclose_quantity(s1.x, s3.x) 1464 assert_allclose_quantity(s1.y, s3.y) 1465 assert_allclose_quantity(s1.z, s3.z) 1466 1467 assert_allclose_quantity(s2.rho, s4.rho) 1468 assert_allclose_quantity(s2.phi, s4.phi) 1469 assert_allclose_quantity(s2.z, s4.z) 1470 1471 1472def test_unit_spherical_roundtrip(): 1473 1474 s1 = UnitSphericalRepresentation(lon=[10., 30.] * u.deg, 1475 lat=[5., 6.] * u.arcmin) 1476 1477 s2 = CartesianRepresentation.from_representation(s1) 1478 1479 s3 = SphericalRepresentation.from_representation(s2) 1480 1481 s4 = UnitSphericalRepresentation.from_representation(s3) 1482 1483 assert_allclose_quantity(s1.lon, s4.lon) 1484 assert_allclose_quantity(s1.lat, s4.lat) 1485 1486 1487def test_no_unnecessary_copies(): 1488 1489 s1 = UnitSphericalRepresentation(lon=[10., 30.] * u.deg, 1490 lat=[5., 6.] * u.arcmin) 1491 s2 = s1.represent_as(UnitSphericalRepresentation) 1492 assert s2 is s1 1493 assert np.may_share_memory(s1.lon, s2.lon) 1494 assert np.may_share_memory(s1.lat, s2.lat) 1495 s3 = s1.represent_as(SphericalRepresentation) 1496 assert np.may_share_memory(s1.lon, s3.lon) 1497 assert np.may_share_memory(s1.lat, s3.lat) 1498 s4 = s1.represent_as(CartesianRepresentation) 1499 s5 = s4.represent_as(CylindricalRepresentation) 1500 assert np.may_share_memory(s5.z, s4.z) 1501 1502 1503def test_representation_repr(): 1504 r1 = SphericalRepresentation(lon=1 * u.deg, lat=2.5 * u.deg, distance=1 * u.kpc) 1505 assert repr(r1) == ('<SphericalRepresentation (lon, lat, distance) in (deg, deg, kpc)\n' 1506 ' (1., 2.5, 1.)>') 1507 1508 r2 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1509 assert repr(r2) == ('<CartesianRepresentation (x, y, z) in kpc\n' 1510 ' (1., 2., 3.)>') 1511 1512 r3 = CartesianRepresentation(x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc) 1513 assert repr(r3) == ('<CartesianRepresentation (x, y, z) in kpc\n' 1514 ' [(1., 4., 9.), (2., 4., 10.), (3., 4., 11.)]>') 1515 1516 1517def test_representation_repr_multi_d(): 1518 """Regression test for #5889.""" 1519 cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit='m') 1520 assert repr(cr) == ( 1521 '<CartesianRepresentation (x, y, z) in m\n' 1522 ' [[(0., 9., 18.), (1., 10., 19.), (2., 11., 20.)],\n' 1523 ' [(3., 12., 21.), (4., 13., 22.), (5., 14., 23.)],\n' 1524 ' [(6., 15., 24.), (7., 16., 25.), (8., 17., 26.)]]>') 1525 # This was broken before. 1526 assert repr(cr.T) == ( 1527 '<CartesianRepresentation (x, y, z) in m\n' 1528 ' [[(0., 9., 18.), (3., 12., 21.), (6., 15., 24.)],\n' 1529 ' [(1., 10., 19.), (4., 13., 22.), (7., 16., 25.)],\n' 1530 ' [(2., 11., 20.), (5., 14., 23.), (8., 17., 26.)]]>') 1531 1532 1533def test_representation_str(): 1534 r1 = SphericalRepresentation(lon=1 * u.deg, lat=2.5 * u.deg, distance=1 * u.kpc) 1535 assert str(r1) == '(1., 2.5, 1.) (deg, deg, kpc)' 1536 1537 r2 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1538 assert str(r2) == '(1., 2., 3.) kpc' 1539 1540 r3 = CartesianRepresentation(x=[1, 2, 3] * u.kpc, y=4 * u.kpc, z=[9, 10, 11] * u.kpc) 1541 assert str(r3) == '[(1., 4., 9.), (2., 4., 10.), (3., 4., 11.)] kpc' 1542 1543 1544def test_representation_str_multi_d(): 1545 """Regression test for #5889.""" 1546 cr = CartesianRepresentation(np.arange(27).reshape(3, 3, 3), unit='m') 1547 assert str(cr) == ( 1548 '[[(0., 9., 18.), (1., 10., 19.), (2., 11., 20.)],\n' 1549 ' [(3., 12., 21.), (4., 13., 22.), (5., 14., 23.)],\n' 1550 ' [(6., 15., 24.), (7., 16., 25.), (8., 17., 26.)]] m') 1551 # This was broken before. 1552 assert str(cr.T) == ( 1553 '[[(0., 9., 18.), (3., 12., 21.), (6., 15., 24.)],\n' 1554 ' [(1., 10., 19.), (4., 13., 22.), (7., 16., 25.)],\n' 1555 ' [(2., 11., 20.), (5., 14., 23.), (8., 17., 26.)]] m') 1556 1557 1558def test_subclass_representation(): 1559 from astropy.coordinates.builtin_frames import ICRS 1560 1561 class Longitude180(Longitude): 1562 def __new__(cls, angle, unit=None, wrap_angle=180 * u.deg, **kwargs): 1563 self = super().__new__(cls, angle, unit=unit, wrap_angle=wrap_angle, 1564 **kwargs) 1565 return self 1566 1567 class SphericalWrap180Representation(SphericalRepresentation): 1568 attr_classes = {'lon': Longitude180, 1569 'lat': Latitude, 1570 'distance': u.Quantity} 1571 1572 class ICRSWrap180(ICRS): 1573 frame_specific_representation_info = ICRS._frame_specific_representation_info.copy() 1574 frame_specific_representation_info[SphericalWrap180Representation] = \ 1575 frame_specific_representation_info[SphericalRepresentation] 1576 default_representation = SphericalWrap180Representation 1577 1578 c = ICRSWrap180(ra=-1 * u.deg, dec=-2 * u.deg, distance=1 * u.m) 1579 assert c.ra.value == -1 1580 assert c.ra.unit is u.deg 1581 assert c.dec.value == -2 1582 assert c.dec.unit is u.deg 1583 1584 1585def test_minimal_subclass(): 1586 # Basically to check what we document works; 1587 # see doc/coordinates/representations.rst 1588 class LogDRepresentation(BaseRepresentation): 1589 attr_classes = {'lon': Longitude, 1590 'lat': Latitude, 1591 'logd': u.Dex} 1592 1593 def to_cartesian(self): 1594 d = self.logd.physical 1595 x = d * np.cos(self.lat) * np.cos(self.lon) 1596 y = d * np.cos(self.lat) * np.sin(self.lon) 1597 z = d * np.sin(self.lat) 1598 return CartesianRepresentation(x=x, y=y, z=z, copy=False) 1599 1600 @classmethod 1601 def from_cartesian(cls, cart): 1602 s = np.hypot(cart.x, cart.y) 1603 r = np.hypot(s, cart.z) 1604 lon = np.arctan2(cart.y, cart.x) 1605 lat = np.arctan2(cart.z, s) 1606 return cls(lon=lon, lat=lat, logd=u.Dex(r), copy=False) 1607 1608 ld1 = LogDRepresentation(90.*u.deg, 0.*u.deg, 1.*u.dex(u.kpc)) 1609 ld2 = LogDRepresentation(lon=90.*u.deg, lat=0.*u.deg, logd=1.*u.dex(u.kpc)) 1610 assert np.all(ld1.lon == ld2.lon) 1611 assert np.all(ld1.lat == ld2.lat) 1612 assert np.all(ld1.logd == ld2.logd) 1613 c = ld1.to_cartesian() 1614 assert_allclose_quantity(c.xyz, [0., 10., 0.] * u.kpc, atol=1.*u.npc) 1615 ld3 = LogDRepresentation.from_cartesian(c) 1616 assert np.all(ld3.lon == ld2.lon) 1617 assert np.all(ld3.lat == ld2.lat) 1618 assert np.all(ld3.logd == ld2.logd) 1619 s = ld1.represent_as(SphericalRepresentation) 1620 assert_allclose_quantity(s.lon, ld1.lon) 1621 assert_allclose_quantity(s.distance, 10.*u.kpc) 1622 assert_allclose_quantity(s.lat, ld1.lat) 1623 1624 with pytest.raises(TypeError): 1625 LogDRepresentation(0.*u.deg, 1.*u.deg) 1626 with pytest.raises(TypeError): 1627 LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), lon=1.*u.deg) 1628 with pytest.raises(TypeError): 1629 LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), True, False) 1630 with pytest.raises(TypeError): 1631 LogDRepresentation(0.*u.deg, 1.*u.deg, 1.*u.dex(u.kpc), foo='bar') 1632 1633 # if we define it a second time, even the qualnames are the same, 1634 # so we raise 1635 with pytest.raises(ValueError): 1636 class LogDRepresentation(BaseRepresentation): 1637 attr_classes = {'lon': Longitude, 1638 'lat': Latitude, 1639 'logr': u.Dex} 1640 1641 1642def test_duplicate_warning(): 1643 from astropy.coordinates.representation import DUPLICATE_REPRESENTATIONS 1644 from astropy.coordinates.representation import REPRESENTATION_CLASSES 1645 1646 with pytest.warns(DuplicateRepresentationWarning): 1647 class UnitSphericalRepresentation(BaseRepresentation): 1648 attr_classes = {'lon': Longitude, 1649 'lat': Latitude} 1650 1651 assert 'unitspherical' in DUPLICATE_REPRESENTATIONS 1652 assert 'unitspherical' not in REPRESENTATION_CLASSES 1653 assert 'astropy.coordinates.representation.UnitSphericalRepresentation' in REPRESENTATION_CLASSES 1654 assert __name__ + '.test_duplicate_warning.<locals>.UnitSphericalRepresentation' in REPRESENTATION_CLASSES 1655 1656 1657class TestCartesianRepresentationWithDifferential: 1658 1659 def test_init_differential(self): 1660 1661 diff = CartesianDifferential(d_x=1 * u.km/u.s, 1662 d_y=2 * u.km/u.s, 1663 d_z=3 * u.km/u.s) 1664 1665 # Check that a single differential gets turned into a 1-item dict. 1666 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1667 differentials=diff) 1668 1669 assert s1.x.unit is u.kpc 1670 assert s1.y.unit is u.kpc 1671 assert s1.z.unit is u.kpc 1672 assert len(s1.differentials) == 1 1673 assert s1.differentials['s'] is diff 1674 1675 # can also pass in an explicit dictionary 1676 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1677 differentials={'s': diff}) 1678 assert len(s1.differentials) == 1 1679 assert s1.differentials['s'] is diff 1680 1681 # using the wrong key will cause it to fail 1682 with pytest.raises(ValueError): 1683 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1684 differentials={'1 / s2': diff}) 1685 1686 # make sure other kwargs are handled properly 1687 s1 = CartesianRepresentation(x=1, y=2, z=3, 1688 differentials=diff, copy=False, unit=u.kpc) 1689 assert len(s1.differentials) == 1 1690 assert s1.differentials['s'] is diff 1691 1692 with pytest.raises(TypeError): # invalid type passed to differentials 1693 CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1694 differentials='garmonbozia') 1695 1696 # And that one can add it to another representation. 1697 s1 = CartesianRepresentation( 1698 CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc), 1699 differentials=diff) 1700 assert len(s1.differentials) == 1 1701 assert s1.differentials['s'] is diff 1702 1703 # make sure differentials can't accept differentials 1704 with pytest.raises(TypeError): 1705 CartesianDifferential(d_x=1 * u.km/u.s, d_y=2 * u.km/u.s, 1706 d_z=3 * u.km/u.s, differentials=diff) 1707 1708 def test_init_differential_compatible(self): 1709 # TODO: more extensive checking of this 1710 1711 # should fail - representation and differential not compatible 1712 diff = SphericalDifferential(d_lon=1 * u.mas/u.yr, 1713 d_lat=2 * u.mas/u.yr, 1714 d_distance=3 * u.km/u.s) 1715 with pytest.raises(TypeError): 1716 CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1717 differentials=diff) 1718 1719 # should succeed - representation and differential are compatible 1720 diff = SphericalCosLatDifferential(d_lon_coslat=1 * u.mas/u.yr, 1721 d_lat=2 * u.mas/u.yr, 1722 d_distance=3 * u.km/u.s) 1723 1724 r1 = SphericalRepresentation(lon=15*u.deg, lat=21*u.deg, 1725 distance=1*u.pc, 1726 differentials=diff) 1727 1728 def test_init_differential_multiple_equivalent_keys(self): 1729 d1 = CartesianDifferential(*[1, 2, 3] * u.km/u.s) 1730 d2 = CartesianDifferential(*[4, 5, 6] * u.km/u.s) 1731 1732 # verify that the check against expected_unit validates against passing 1733 # in two different but equivalent keys 1734 with pytest.raises(ValueError): 1735 r1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1736 differentials={'s': d1, 'yr': d2}) 1737 1738 def test_init_array_broadcasting(self): 1739 1740 arr1 = np.arange(8).reshape(4, 2) * u.km/u.s 1741 diff = CartesianDifferential(d_x=arr1, d_y=arr1, d_z=arr1) 1742 1743 # shapes aren't compatible 1744 arr2 = np.arange(27).reshape(3, 9) * u.kpc 1745 with pytest.raises(ValueError): 1746 rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, 1747 differentials=diff) 1748 1749 arr2 = np.arange(8).reshape(4, 2) * u.kpc 1750 rep = CartesianRepresentation(x=arr2, y=arr2, z=arr2, 1751 differentials=diff) 1752 1753 assert rep.x.unit is u.kpc 1754 assert rep.y.unit is u.kpc 1755 assert rep.z.unit is u.kpc 1756 assert len(rep.differentials) == 1 1757 assert rep.differentials['s'] is diff 1758 1759 assert rep.xyz.shape == rep.differentials['s'].d_xyz.shape 1760 1761 def test_reprobj(self): 1762 1763 # should succeed - representation and differential are compatible 1764 diff = SphericalCosLatDifferential(d_lon_coslat=1 * u.mas/u.yr, 1765 d_lat=2 * u.mas/u.yr, 1766 d_distance=3 * u.km/u.s) 1767 1768 r1 = SphericalRepresentation(lon=15*u.deg, lat=21*u.deg, 1769 distance=1*u.pc, 1770 differentials=diff) 1771 1772 r2 = CartesianRepresentation.from_representation(r1) 1773 assert r2.get_name() == 'cartesian' 1774 assert not r2.differentials 1775 1776 r3 = SphericalRepresentation(r1) 1777 assert r3.differentials 1778 assert representation_equal(r3, r1) 1779 1780 def test_readonly(self): 1781 1782 s1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc) 1783 1784 with pytest.raises(AttributeError): # attribute is not settable 1785 s1.differentials = 'thing' 1786 1787 def test_represent_as(self): 1788 1789 diff = CartesianDifferential(d_x=1 * u.km/u.s, 1790 d_y=2 * u.km/u.s, 1791 d_z=3 * u.km/u.s) 1792 rep1 = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1793 differentials=diff) 1794 1795 # Only change the representation, drop the differential 1796 new_rep = rep1.represent_as(SphericalRepresentation) 1797 assert new_rep.get_name() == 'spherical' 1798 assert not new_rep.differentials # dropped 1799 1800 # Pass in separate classes for representation, differential 1801 new_rep = rep1.represent_as(SphericalRepresentation, 1802 SphericalCosLatDifferential) 1803 assert new_rep.get_name() == 'spherical' 1804 assert new_rep.differentials['s'].get_name() == 'sphericalcoslat' 1805 1806 # Pass in a dictionary for the differential classes 1807 new_rep = rep1.represent_as(SphericalRepresentation, 1808 {'s': SphericalCosLatDifferential}) 1809 assert new_rep.get_name() == 'spherical' 1810 assert new_rep.differentials['s'].get_name() == 'sphericalcoslat' 1811 1812 # make sure represent_as() passes through the differentials 1813 for name in REPRESENTATION_CLASSES: 1814 if name == 'radial': 1815 # TODO: Converting a CartesianDifferential to a 1816 # RadialDifferential fails, even on `main` 1817 continue 1818 elif name.endswith("geodetic"): 1819 # TODO: Geodetic representations do not have differentials yet 1820 continue 1821 new_rep = rep1.represent_as(REPRESENTATION_CLASSES[name], 1822 DIFFERENTIAL_CLASSES[name]) 1823 assert new_rep.get_name() == name 1824 assert len(new_rep.differentials) == 1 1825 assert new_rep.differentials['s'].get_name() == name 1826 1827 with pytest.raises(ValueError) as excinfo: 1828 rep1.represent_as('name') 1829 assert 'use frame object' in str(excinfo.value) 1830 1831 @pytest.mark.parametrize('sph_diff,usph_diff', [ 1832 (SphericalDifferential, UnitSphericalDifferential), 1833 (SphericalCosLatDifferential, UnitSphericalCosLatDifferential)]) 1834 def test_represent_as_unit_spherical_with_diff(self, sph_diff, usph_diff): 1835 """Test that differential angles are correctly reduced.""" 1836 diff = CartesianDifferential(d_x=1 * u.km/u.s, 1837 d_y=2 * u.km/u.s, 1838 d_z=3 * u.km/u.s) 1839 rep = CartesianRepresentation(x=1 * u.kpc, y=2 * u.kpc, z=3 * u.kpc, 1840 differentials=diff) 1841 sph = rep.represent_as(SphericalRepresentation, sph_diff) 1842 usph = rep.represent_as(UnitSphericalRepresentation, usph_diff) 1843 assert components_equal(usph, sph.represent_as(UnitSphericalRepresentation)) 1844 assert components_equal(usph.differentials['s'], 1845 sph.differentials['s'].represent_as(usph_diff)) 1846 # Just to be sure components_equal and the represent_as work as advertised, 1847 # a sanity check: d_lat is always defined and should be the same. 1848 assert_array_equal(sph.differentials['s'].d_lat, 1849 usph.differentials['s'].d_lat) 1850 1851 def test_getitem(self): 1852 1853 d = CartesianDifferential(d_x=np.arange(10) * u.m/u.s, 1854 d_y=-np.arange(10) * u.m/u.s, 1855 d_z=1. * u.m/u.s) 1856 s = CartesianRepresentation(x=np.arange(10) * u.m, 1857 y=-np.arange(10) * u.m, 1858 z=3 * u.km, 1859 differentials=d) 1860 1861 s_slc = s[2:8:2] 1862 s_dif = s_slc.differentials['s'] 1863 1864 assert_allclose_quantity(s_slc.x, [2, 4, 6] * u.m) 1865 assert_allclose_quantity(s_slc.y, [-2, -4, -6] * u.m) 1866 assert_allclose_quantity(s_slc.z, [3, 3, 3] * u.km) 1867 1868 assert_allclose_quantity(s_dif.d_x, [2, 4, 6] * u.m/u.s) 1869 assert_allclose_quantity(s_dif.d_y, [-2, -4, -6] * u.m/u.s) 1870 assert_allclose_quantity(s_dif.d_z, [1, 1, 1] * u.m/u.s) 1871 1872 def test_setitem(self): 1873 d = CartesianDifferential(d_x=np.arange(5) * u.m/u.s, 1874 d_y=-np.arange(5) * u.m/u.s, 1875 d_z=1. * u.m/u.s) 1876 s = CartesianRepresentation(x=np.arange(5) * u.m, 1877 y=-np.arange(5) * u.m, 1878 z=3 * u.km, 1879 differentials=d) 1880 s[:2] = s[2] 1881 assert_array_equal(s.x, [2, 2, 2, 3, 4] * u.m) 1882 assert_array_equal(s.y, [-2, -2, -2, -3, -4] * u.m) 1883 assert_array_equal(s.z, [3, 3, 3, 3, 3] * u.km) 1884 assert_array_equal(s.differentials['s'].d_x, 1885 [2, 2, 2, 3, 4] * u.m/u.s) 1886 assert_array_equal(s.differentials['s'].d_y, 1887 [-2, -2, -2, -3, -4] * u.m/u.s) 1888 assert_array_equal(s.differentials['s'].d_z, 1889 [1, 1, 1, 1, 1] * u.m/u.s) 1890 1891 s2 = s.represent_as(SphericalRepresentation, 1892 SphericalDifferential) 1893 1894 s[0] = s2[3] 1895 assert_allclose_quantity(s.x, [3, 2, 2, 3, 4] * u.m) 1896 assert_allclose_quantity(s.y, [-3, -2, -2, -3, -4] * u.m) 1897 assert_allclose_quantity(s.z, [3, 3, 3, 3, 3] * u.km) 1898 assert_allclose_quantity(s.differentials['s'].d_x, 1899 [3, 2, 2, 3, 4] * u.m/u.s) 1900 assert_allclose_quantity(s.differentials['s'].d_y, 1901 [-3, -2, -2, -3, -4] * u.m/u.s) 1902 assert_allclose_quantity(s.differentials['s'].d_z, 1903 [1, 1, 1, 1, 1] * u.m/u.s) 1904 1905 s3 = CartesianRepresentation(s.xyz, differentials={ 1906 's': d, 1907 's2': CartesianDifferential(np.ones((3, 5))*u.m/u.s**2)}) 1908 with pytest.raises(ValueError, match='same differentials'): 1909 s[0] = s3[2] 1910 1911 s4 = SphericalRepresentation(0.*u.deg, 0.*u.deg, 1.*u.kpc, 1912 differentials=RadialDifferential( 1913 10*u.km/u.s)) 1914 with pytest.raises(ValueError, match='loss of information'): 1915 s[0] = s4 1916 1917 def test_transform(self): 1918 d1 = CartesianDifferential(d_x=[1, 2] * u.km/u.s, 1919 d_y=[3, 4] * u.km/u.s, 1920 d_z=[5, 6] * u.km/u.s) 1921 r1 = CartesianRepresentation(x=[1, 2] * u.kpc, 1922 y=[3, 4] * u.kpc, 1923 z=[5, 6] * u.kpc, 1924 differentials=d1) 1925 1926 r2 = r1.transform(matrices["general"]) 1927 d2 = r2.differentials['s'] 1928 assert_allclose_quantity(d2.d_x, [22., 28]*u.km/u.s) 1929 assert_allclose_quantity(d2.d_y, [49, 64]*u.km/u.s) 1930 assert_allclose_quantity(d2.d_z, [76, 100.]*u.km/u.s) 1931 1932 def test_with_differentials(self): 1933 # make sure with_differential correctly creates a new copy with the same 1934 # differential 1935 cr = CartesianRepresentation([1, 2, 3]*u.kpc) 1936 diff = CartesianDifferential([.1, .2, .3]*u.km/u.s) 1937 cr2 = cr.with_differentials(diff) 1938 assert cr.differentials != cr2.differentials 1939 assert cr2.differentials['s'] is diff 1940 1941 # make sure it works even if a differential is present already 1942 diff2 = CartesianDifferential([.1, .2, .3]*u.m/u.s) 1943 cr3 = CartesianRepresentation([1, 2, 3]*u.kpc, differentials=diff) 1944 cr4 = cr3.with_differentials(diff2) 1945 assert cr4.differentials['s'] != cr3.differentials['s'] 1946 assert cr4.differentials['s'] == diff2 1947 1948 # also ensure a *scalar* differential will works 1949 cr5 = cr.with_differentials(diff) 1950 assert len(cr5.differentials) == 1 1951 assert cr5.differentials['s'] == diff 1952 1953 # make sure we don't update the original representation's dict 1954 d1 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km/u.s) 1955 d2 = CartesianDifferential(*np.random.random((3, 5)), unit=u.km/u.s**2) 1956 r1 = CartesianRepresentation(*np.random.random((3, 5)), unit=u.pc, 1957 differentials=d1) 1958 1959 r2 = r1.with_differentials(d2) 1960 assert r1.differentials['s'] is r2.differentials['s'] 1961 assert 's2' not in r1.differentials 1962 assert 's2' in r2.differentials 1963 1964 1965def test_repr_with_differentials(): 1966 diff = CartesianDifferential([.1, .2, .3]*u.km/u.s) 1967 cr = CartesianRepresentation([1, 2, 3]*u.kpc, differentials=diff) 1968 assert "has differentials w.r.t.: 's'" in repr(cr) 1969 1970 1971def test_to_cartesian(): 1972 """ 1973 Test that to_cartesian drops the differential. 1974 """ 1975 sd = SphericalDifferential(d_lat=1*u.deg, d_lon=2*u.deg, d_distance=10*u.m) 1976 sr = SphericalRepresentation(lat=1*u.deg, lon=2*u.deg, distance=10*u.m, 1977 differentials=sd) 1978 1979 cart = sr.to_cartesian() 1980 assert cart.get_name() == 'cartesian' 1981 assert not cart.differentials 1982 1983 1984@pytest.fixture 1985def unitphysics(): 1986 """ 1987 This fixture is used 1988 """ 1989 had_unit = False 1990 if hasattr(PhysicsSphericalRepresentation, '_unit_representation'): 1991 orig = PhysicsSphericalRepresentation._unit_representation 1992 had_unit = True 1993 1994 class UnitPhysicsSphericalRepresentation(BaseRepresentation): 1995 attr_classes = {'phi': Angle, 1996 'theta': Angle} 1997 1998 def __init__(self, *args, copy=True, **kwargs): 1999 super().__init__(*args, copy=copy, **kwargs) 2000 2001 # Wrap/validate phi/theta 2002 if copy: 2003 self._phi = self._phi.wrap_at(360 * u.deg) 2004 else: 2005 # necessary because the above version of `wrap_at` has to be a copy 2006 self._phi.wrap_at(360 * u.deg, inplace=True) 2007 2008 if np.any(self._theta < 0.*u.deg) or np.any(self._theta > 180.*u.deg): 2009 raise ValueError('Inclination angle(s) must be within ' 2010 '0 deg <= angle <= 180 deg, ' 2011 'got {}'.format(self._theta.to(u.degree))) 2012 2013 @property 2014 def phi(self): 2015 return self._phi 2016 2017 @property 2018 def theta(self): 2019 return self._theta 2020 2021 def unit_vectors(self): 2022 sinphi, cosphi = np.sin(self.phi), np.cos(self.phi) 2023 sintheta, costheta = np.sin(self.theta), np.cos(self.theta) 2024 return { 2025 'phi': CartesianRepresentation(-sinphi, cosphi, 0., copy=False), 2026 'theta': CartesianRepresentation(costheta*cosphi, 2027 costheta*sinphi, 2028 -sintheta, copy=False)} 2029 2030 def scale_factors(self): 2031 sintheta = np.sin(self.theta) 2032 l = np.broadcast_to(1.*u.one, self.shape, subok=True) 2033 return {'phi', sintheta, 2034 'theta', l} 2035 2036 def to_cartesian(self): 2037 x = np.sin(self.theta) * np.cos(self.phi) 2038 y = np.sin(self.theta) * np.sin(self.phi) 2039 z = np.cos(self.theta) 2040 2041 return CartesianRepresentation(x=x, y=y, z=z, copy=False) 2042 2043 @classmethod 2044 def from_cartesian(cls, cart): 2045 """ 2046 Converts 3D rectangular cartesian coordinates to spherical polar 2047 coordinates. 2048 """ 2049 s = np.hypot(cart.x, cart.y) 2050 2051 phi = np.arctan2(cart.y, cart.x) 2052 theta = np.arctan2(s, cart.z) 2053 2054 return cls(phi=phi, theta=theta, copy=False) 2055 2056 def norm(self): 2057 return u.Quantity(np.ones(self.shape), u.dimensionless_unscaled, 2058 copy=False) 2059 2060 PhysicsSphericalRepresentation._unit_representation = UnitPhysicsSphericalRepresentation 2061 yield UnitPhysicsSphericalRepresentation 2062 2063 if had_unit: 2064 PhysicsSphericalRepresentation._unit_representation = orig 2065 else: 2066 del PhysicsSphericalRepresentation._unit_representation 2067 2068 # remove from the module-level representations, if present 2069 REPRESENTATION_CLASSES.pop(UnitPhysicsSphericalRepresentation.get_name(), None) 2070 2071 2072def test_unitphysics(unitphysics): 2073 obj = unitphysics(phi=0*u.deg, theta=10*u.deg) 2074 objkw = unitphysics(phi=0*u.deg, theta=10*u.deg) 2075 assert objkw.phi == obj.phi 2076 assert objkw.theta == obj.theta 2077 2078 asphys = obj.represent_as(PhysicsSphericalRepresentation) 2079 assert asphys.phi == obj.phi 2080 assert_allclose(asphys.theta, obj.theta) 2081 assert_allclose_quantity(asphys.r, 1*u.dimensionless_unscaled) 2082 2083 assph = obj.represent_as(SphericalRepresentation) 2084 assert assph.lon == obj.phi 2085 assert assph.lat == 80*u.deg 2086 assert_allclose_quantity(assph.distance, 1*u.dimensionless_unscaled) 2087 2088 with pytest.raises(TypeError, match='got multiple values'): 2089 unitphysics(1*u.deg, 2*u.deg, theta=10) 2090 2091 with pytest.raises(TypeError, match='unexpected keyword.*parrot'): 2092 unitphysics(1*u.deg, 2*u.deg, parrot=10) 2093 2094 2095def test_distance_warning(recwarn): 2096 SphericalRepresentation(1*u.deg, 2*u.deg, 1*u.kpc) 2097 with pytest.raises(ValueError) as excinfo: 2098 SphericalRepresentation(1*u.deg, 2*u.deg, -1*u.kpc) 2099 assert 'Distance must be >= 0' in str(excinfo.value) 2100 # second check is because the "originating" ValueError says the above, 2101 # while the representation one includes the below 2102 assert 'you must explicitly pass' in str(excinfo.value) 2103 2104 2105def test_dtype_preservation_in_indexing(): 2106 # Regression test for issue #8614 (fixed in #8876) 2107 xyz = np.array([[1, 0, 0], [0.9, 0.1, 0]], dtype='f4') 2108 cr = CartesianRepresentation(xyz, xyz_axis=-1, unit="km") 2109 assert cr.xyz.dtype == xyz.dtype 2110 cr0 = cr[0] 2111 # This used to fail. 2112 assert cr0.xyz.dtype == xyz.dtype 2113 2114 2115class TestInfo: 2116 def setup_class(cls): 2117 cls.rep = SphericalRepresentation([0, 1]*u.deg, [2, 3]*u.deg, 2118 10*u.pc) 2119 cls.diff = SphericalDifferential([10, 20]*u.mas/u.yr, 2120 [30, 40]*u.mas/u.yr, 2121 [50, 60]*u.km/u.s) 2122 cls.rep_w_diff = SphericalRepresentation(cls.rep, 2123 differentials=cls.diff) 2124 2125 def test_info_unit(self): 2126 assert self.rep.info.unit == 'deg, deg, pc' 2127 assert self.diff.info.unit == 'mas / yr, mas / yr, km / s' 2128 assert self.rep_w_diff.info.unit == 'deg, deg, pc' 2129 2130 @pytest.mark.parametrize('item', ['rep', 'diff', 'rep_w_diff']) 2131 def test_roundtrip(self, item): 2132 rep_or_diff = getattr(self, item) 2133 as_dict = rep_or_diff.info._represent_as_dict() 2134 new = rep_or_diff.__class__.info._construct_from_dict(as_dict) 2135 assert np.all(representation_equal(new, rep_or_diff)) 2136 2137 2138@pytest.mark.parametrize('cls', 2139 [SphericalDifferential, 2140 SphericalCosLatDifferential, 2141 CylindricalDifferential, 2142 PhysicsSphericalDifferential, 2143 UnitSphericalDifferential, 2144 UnitSphericalCosLatDifferential]) 2145def test_differential_norm_noncartesian(cls): 2146 # The norm of a non-Cartesian differential without specifying `base` should error 2147 rep = cls(0, 0, 0) 2148 with pytest.raises(ValueError, match=r"`base` must be provided .* " + cls.__name__): 2149 rep.norm() 2150 2151 2152def test_differential_norm_radial(): 2153 # Unlike most non-Cartesian differentials, the norm of a radial differential does not require `base` 2154 rep = RadialDifferential(1*u.km/u.s) 2155 assert_allclose_quantity(rep.norm(), 1*u.km/u.s) 2156