def setup_class(cls): # We set up some representations with, on purpose, copy=False, # so we can check that broadcasting is handled correctly. lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays cls.s0 = SphericalRepresentation( lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc, copy=False) cls.diff = SphericalDifferential( d_lon=np.ones(cls.s0.shape) * u.mas / u.yr, d_lat=np.ones(cls.s0.shape) * u.mas / u.yr, d_distance=np.ones(cls.s0.shape) * u.km / u.s, copy=False) cls.s0 = cls.s0.with_differentials(cls.diff) # With unequal arrays -> these will be broadcasted. cls.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, differentials=cls.diff, copy=False) # For completeness on some tests, also a cartesian one cls.c0 = cls.s0.to_cartesian()
def test_regression_8615(): # note this is a "higher-level" symptom of the problem that a test now moved # to pyerfa (erfa/tests/test_erfa:test_float32_input) is testing for, but we keep # it here as well due to being a more practical version of the issue. crf = CartesianRepresentation(np.array([3, 0, 4], dtype=float) * u.pc) srf = SphericalRepresentation.from_cartesian(crf) # does not error in 8615 cr = CartesianRepresentation(np.array([3, 0, 4], dtype='f4') * u.pc) sr = SphericalRepresentation.from_cartesian(cr) # errors in 8615 assert_quantity_allclose(sr.distance, 5 * u.pc) assert_quantity_allclose(srf.distance, 5 * u.pc)
def test_shape_setting(self): # Shape-setting should be on the object itself, since copying removes # zero-strides due to broadcasting. We reset the objects at the end. self.s0.shape = (2, 3, 7) assert self.s0.shape == (2, 3, 7) assert self.s0.lon.shape == (2, 3, 7) assert self.s0.lat.shape == (2, 3, 7) assert self.s0.distance.shape == (2, 3, 7) assert self.diff.shape == (2, 3, 7) assert self.diff.d_lon.shape == (2, 3, 7) assert self.diff.d_lat.shape == (2, 3, 7) assert self.diff.d_distance.shape == (2, 3, 7) # this works with the broadcasting. self.s1.shape = (2, 3, 7) assert self.s1.shape == (2, 3, 7) assert self.s1.lon.shape == (2, 3, 7) assert self.s1.lat.shape == (2, 3, 7) assert self.s1.distance.shape == (2, 3, 7) assert self.s1.distance.strides == (0, 0, 0) # but this one does not. oldshape = self.s1.shape with pytest.raises(ValueError): self.s1.shape = (1, ) with pytest.raises(AttributeError): self.s1.shape = (42, ) assert self.s1.shape == oldshape assert self.s1.lon.shape == oldshape assert self.s1.lat.shape == oldshape assert self.s1.distance.shape == oldshape # Finally, a more complicated one that checks that things get reset # properly if it is not the first component that fails. s2 = SphericalRepresentation(self.s1.lon.copy(), self.s1.lat, self.s1.distance, copy=False) assert 0 not in s2.lon.strides assert 0 in s2.lat.strides with pytest.raises(AttributeError): s2.shape = (42, ) assert s2.shape == oldshape assert s2.lon.shape == oldshape assert s2.lat.shape == oldshape assert s2.distance.shape == oldshape assert 0 not in s2.lon.strides assert 0 in s2.lat.strides self.setup()
def make_earth_vis_grids(nside=32, n_reps=1): from acis_taco import calc_earth_vis, RAD_EARTH, acis_taco hp = astropy_healpix.HEALPix(nside=nside, order='nested') npix = astropy_healpix.nside_to_npix(nside) print(f'npix={npix}') lons, lats = hp.healpix_to_lonlat(np.arange(npix)) time0 = time.time() # Allow randomization between altitudes for i_rep in range(n_reps): vis_arrays = [] # Randomize the ray-trace points for each rep acis_taco._RANDOM_SALT = None acis_taco.SPHERE_XYZ = acis_taco.random_hemisphere(acis_taco.N_SPHERE) acis_taco.SPHERE_X = acis_taco.SPHERE_XYZ[:, 0].copy() for i_alt, alt in enumerate(alts): srs = SphericalRepresentation(lon=lons, lat=lats, distance=alt + RAD_EARTH) xyzs = srs.to_cartesian() peb_xs = xyzs.x.to_value() peb_ys = xyzs.y.to_value() peb_zs = xyzs.z.to_value() vis = [] for peb_x, peb_y, peb_z in zip(peb_xs, peb_ys, peb_zs): _, illums, _ = calc_earth_vis( p_earth_body=[peb_x, peb_y, peb_z]) vis.append(np.sum(illums)) vis = np.array(vis) vis_arrays.append(vis) vis_grid = np.vstack(vis_arrays) if i_alt % 10 == 0: print( f'alt={alt / 1000:.2f} km at dt={time.time() - time0:.1f}') ii = 1 while True: filename = Path(f'earth_vis_grid_nside{nside}_rep{ii}.npy') if filename.exists(): ii += 1 continue else: print(f'Saving {filename}') np.save(filename, vis_grid) break return vis_grid
def test_cirs_to_altaz(): """ Check the basic CIRS<->AltAz transforms. More thorough checks implicitly happen in `test_iau_fullstack` """ from astropy.coordinates import EarthLocation ra, dec, dist = randomly_sample_sphere(200) cirs = CIRS(ra=ra, dec=dec, obstime='J2000') crepr = SphericalRepresentation(lon=ra, lat=dec, distance=dist) cirscart = CIRS(crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation) loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) altazframe = AltAz(location=loc, obstime=Time('J2005')) cirs2 = cirs.transform_to(altazframe).transform_to(cirs) cirs3 = cirscart.transform_to(altazframe).transform_to(cirs) # check round-tripping assert_allclose(cirs.ra, cirs2.ra) assert_allclose(cirs.dec, cirs2.dec) assert_allclose(cirs.ra, cirs3.ra) assert_allclose(cirs.dec, cirs3.dec)
def test_cirs_to_hadec(): """ Check the basic CIRS<->HADec transforms. """ from astropy.coordinates import EarthLocation usph = golden_spiral_grid(200) dist = np.linspace(0.5, 1, len(usph)) * u.pc cirs = CIRS(usph, obstime='J2000') crepr = SphericalRepresentation(lon=usph.lon, lat=usph.lat, distance=dist) cirscart = CIRS(crepr, obstime=cirs.obstime, representation_type=CartesianRepresentation) loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m) hadecframe = HADec(location=loc, obstime=Time('J2005')) cirs2 = cirs.transform_to(hadecframe).transform_to(cirs) cirs3 = cirscart.transform_to(hadecframe).transform_to(cirs) # check round-tripping assert_allclose(cirs.ra, cirs2.ra) assert_allclose(cirs.dec, cirs2.dec) assert_allclose(cirs.ra, cirs3.ra) assert_allclose(cirs.dec, cirs3.dec)
def __new__(cls, *args, **kwargs): origin_frame = kwargs.pop('north', None) if origin_frame is None: raise TypeError( "Can't initialize an NorthOffsetFrame without a `north` keyword." ) if hasattr(origin_frame, 'frame'): origin_frame = origin_frame.frame rep = origin_frame.spherical lon = rep.lon lat = rep.lat if lat > 0 * u.deg: lat = lat - 90 * u.deg rotation = None else: lon = lon - 180 * u.deg lat = -90 * u.deg - lat rotation = 180 * u.deg new_rep = SphericalRepresentation(lon=lon, lat=lat, distance=rep.distance) new_origin = origin_frame.realize_frame(new_rep) kwargs['origin'] = new_origin kwargs['rotation'] = rotation return SkyOffsetFrame(*args, **kwargs)
def setup_grid(self, wavelength=656*u.nm): # use HEALPIX to get evenly sized tiles NSIDE = hp.npix2nside(self.ntiles) colat, lon = hp.pix2ang(NSIDE, np.arange(0, self.ntiles)) # co-latitude theta_values = u.Quantity(colat, unit=u.rad) # longitude phi_values = u.Quantity(lon, unit=u.rad) # the following formulae use the Roche approximation and assume # solid body rotation # solve for radius of rotating star at these co-latitudes if self.distortion: radii = self.radius*np.array([newton(surface, 1.01, args=(self.omega, x)) for x in theta_values]) else: radii = self.radius*np.ones(self.ntiles) # and effective gravities geff = np.sqrt((-const.G*self.mass/radii**2 + self.Omega**2 * radii * np.sin(theta_values)**2)**2 + self.Omega**4 * radii**2 * np.sin(theta_values)**2 * np.cos(theta_values)**2) # now make a ntiles sized CartesianRepresentation of positions self.tile_locs = SphericalRepresentation(phi_values, 90*u.deg-theta_values, radii).to_cartesian() # normal to tile is the direction of the derivate of the potential # this is the vector form of geff above # the easiest way to express it is that it differs from (r, theta, phi) # by a small amount in the theta direction epsilon x = radii/self.radius a = 1./x**2 - (8./27.)*self.omega**2 * x * np.sin(theta_values)**2 b = np.sqrt( (-1./x**2 + (8./27)*self.omega**2 * x * np.sin(theta_values)**2)**2 + ((8./27)*self.omega**2 * x * np.sin(theta_values) * np.cos(theta_values))**2 ) epsilon = np.arccos(a/b) self.tile_dirs = UnitSphericalRepresentation(phi_values, 90*u.deg - theta_values - epsilon) self.tile_dirs = self.tile_dirs.to_cartesian() # and ntiles sized arrays of tile properties tile_temperatures = 2000.0 * u.K * (geff / geff.max())**self.beta # fluxes, not accounting for limb darkening self.tile_scales = np.ones(self.ntiles) self.tile_fluxes = blackbody_nu(wavelength, tile_temperatures) # tile areas spher = self.tile_locs.represent_as(SphericalRepresentation) self.tile_areas = spher.distance**2 * hp.nside2pixarea(NSIDE) * u.rad * u.rad omega_vec = CartesianRepresentation( u.Quantity([0.0, 0.0, self.Omega.value], unit=self.Omega.unit) ) # get velocities of tiles self.tile_velocities = cross(omega_vec, self.tile_locs)
def test_shape_setting(self): # Shape-setting should be on the object itself, since copying removes # zero-strides due to broadcasting. We reset the objects at the end. self.s0.shape = (2, 3, 7) assert self.s0.shape == (2, 3, 7) assert self.s0.lon.shape == (2, 3, 7) assert self.s0.lat.shape == (2, 3, 7) assert self.s0.distance.shape == (2, 3, 7) assert self.diff.shape == (2, 3, 7) assert self.diff.d_lon.shape == (2, 3, 7) assert self.diff.d_lat.shape == (2, 3, 7) assert self.diff.d_distance.shape == (2, 3, 7) # this works with the broadcasting. self.s1.shape = (2, 3, 7) assert self.s1.shape == (2, 3, 7) assert self.s1.lon.shape == (2, 3, 7) assert self.s1.lat.shape == (2, 3, 7) assert self.s1.distance.shape == (2, 3, 7) assert self.s1.distance.strides == (0, 0, 0) # but this one does not. oldshape = self.s1.shape with pytest.raises(AttributeError): self.s1.shape = (42,) assert self.s1.shape == oldshape assert self.s1.lon.shape == oldshape assert self.s1.lat.shape == oldshape assert self.s1.distance.shape == oldshape # Finally, a more complicated one that checks that things get reset # properly if it is not the first component that fails. s2 = SphericalRepresentation(self.s1.lon.copy(), self.s1.lat, self.s1.distance, copy=False) assert 0 not in s2.lon.strides assert 0 in s2.lat.strides with pytest.raises(AttributeError): s2.shape = (42,) assert s2.shape == oldshape assert s2.lon.shape == oldshape assert s2.lat.shape == oldshape assert s2.distance.shape == oldshape assert 0 not in s2.lon.strides assert 0 in s2.lat.strides self.setup()
def test_regression_6236(): # sunpy changes its representation upon initialisation of a frame, # including via `realize_frame`. Ensure this works. class MyFrame(BaseCoordinateFrame): default_representation = CartesianRepresentation my_attr = QuantityAttribute(default=0, unit=u.m) class MySpecialFrame(MyFrame): def __init__(self, *args, **kwargs): _rep_kwarg = kwargs.get('representation_type', None) super().__init__(*args, **kwargs) if not _rep_kwarg: self.representation_type = self.default_representation self._data = self.data.represent_as(self.representation_type) rep1 = UnitSphericalRepresentation([0., 1] * u.deg, [2., 3.] * u.deg) rep2 = SphericalRepresentation([10., 11] * u.deg, [12., 13.] * u.deg, [14., 15.] * u.kpc) mf1 = MyFrame(rep1, my_attr=1. * u.km) mf2 = mf1.realize_frame(rep2) # Normally, data is stored as is, but the representation gets set to a # default, even if a different representation instance was passed in. # realize_frame should do the same. Just in case, check attrs are passed. assert mf1.data is rep1 assert mf2.data is rep2 assert mf1.representation_type is CartesianRepresentation assert mf2.representation_type is CartesianRepresentation assert mf2.my_attr == mf1.my_attr # It should be independent of whether I set the reprensentation explicitly mf3 = MyFrame(rep1, my_attr=1. * u.km, representation_type='unitspherical') mf4 = mf3.realize_frame(rep2) assert mf3.data is rep1 assert mf4.data is rep2 assert mf3.representation_type is UnitSphericalRepresentation assert mf4.representation_type is CartesianRepresentation assert mf4.my_attr == mf3.my_attr # This should be enough to help sunpy, but just to be sure, a test # even closer to what is done there, i.e., transform the representation. msf1 = MySpecialFrame(rep1, my_attr=1. * u.km) msf2 = msf1.realize_frame(rep2) assert msf1.data is not rep1 # Gets transformed to Cartesian. assert msf2.data is not rep2 assert type(msf1.data) is CartesianRepresentation assert type(msf2.data) is CartesianRepresentation assert msf1.representation_type is CartesianRepresentation assert msf2.representation_type is CartesianRepresentation assert msf2.my_attr == msf1.my_attr # And finally a test where the input is not transformed. msf3 = MySpecialFrame(rep1, my_attr=1. * u.km, representation_type='unitspherical') msf4 = msf3.realize_frame(rep2) assert msf3.data is rep1 assert msf4.data is not rep2 assert msf3.representation_type is UnitSphericalRepresentation assert msf4.representation_type is CartesianRepresentation assert msf4.my_attr == msf3.my_attr
def __getcoordinatearrays__(rotate=True, representation='Cylindrical', **kwargs): """ This function will create a 2D/3D array from the requested shape in kwargs['shape']. It can either return this array into cartesian, polar/cylindrical or spherical coordinates. Using the optional rotate keyword the array can also be rotated into the plane of the sky. This requires the position angle, 'PA', and the inclinatio, 'Incl'. Parameters ---------- rotate : 'True' | 'False' This will either return the rotated or non-rotated array. representation : 'Cylindrical' | 'Cartesian' | 'Spherical' Representation to use for the returned array. Returns ------- 2 or 3 numpy arrays corresponding to the rho, phi, (z) array, the x, y, (z) array or the rho, lon, (lat array). """ # create x, y ,and z arrays tshape = np.array(kwargs['shape']) shape = (tshape[-1], tshape[-2], np.max(tshape)) Indices = np.indices(shape, dtype=float) # translate the array Indices[0] = Indices[0] - kwargs['par']['Xcen'] Indices[1] = Indices[1] - kwargs['par']['Ycen'] Indices[2] = Indices[2] - int(kwargs['shape'][2] / 2.) # make a cartesian representation for matrix rotation CarRep = CartesianRepresentation(Indices, unit=u.pix) if rotate: # rotation due to PA (NOTE different axis definition) CarRep = CarRep.transform( rm(kwargs['par']['PA'] + np.pi / 2, axis='z', unit=u.rad)) # rotation due to inclination (NOTE different axis definition) CarRep = CarRep.transform( rm(kwargs['par']['Incl'], axis='x', unit=u.rad)) else: # rotate by 90 degrees because of definition of PA CarRep = CarRep.transform(rm(np.pi / 2, axis='z', unit=u.rad)) # the representation to use and the return values if representation == 'Cartesian': return CarRep.x.value, CarRep.y.value, CarRep.z.value elif representation == 'Cylindrical': Rep = CylindricalRepresentation.from_cartesian(CarRep) return Rep.rho.value, Rep.phi.value, Rep.z.value elif representation == 'Spherical': Rep = SphericalRepresentation.from_cartesian(CarRep) return Rep.distance.value, Rep.lon.value, Rep.lat.value
def hadec_to_radec(ha, dec, t): """ Convert apparent HA, Dec to J2000 RA, Dec :param ha: hour angle with unit :param dec: declination with unit :param Time/str t: Observing time :return: SkyCoord object of J2000 coordinates """ # Convert time to Time object if given as string if isinstance(t, str): t = Time(t) # create spherical representation of ITRS coordinates of given ha, dec itrs_spherical = SphericalRepresentation(WSRT_LOC.lon - ha, dec, 1.) # create ITRS object, which requires cartesian input coord = SkyCoord(itrs_spherical.to_cartesian(), frame='itrs', obstime=t) # convert to J2000 return coord.icrs.ra, coord.icrs.dec
def obsgeo_to_frame(obsgeo, obstime): """ Convert a WCS obsgeo property into an `~builtin_frames.ITRS` coordinate frame. Parameters ---------- obsgeo : array-like A shape ``(6, )`` array representing ``OBSGEO-[XYZ], OBSGEO-[BLH]`` as returned by ``WCS.wcs.obsgeo``. obstime : time-like The time assiociated with the coordinate, will be passed to `~.builtin_frames.ITRS` as the obstime keyword. Returns ------- `~.builtin_frames.ITRS` An `~.builtin_frames.ITRS` coordinate frame representing the coordinates. Notes ----- The obsgeo array as accessed on a `.WCS` object is a length 6 numpy array where the first three elements are the coordinate in a cartesian representation and the second 3 are the coordinate in a spherical representation. This function priorities reading the cartesian coordinates, and will only read the spherical coordinates if the cartesian coordinates are either all zero or any of the cartesian coordinates are non-finite. In the case where both the spherical and cartesian coordinates have some non-finite values the spherical coordinates will be returned with the non-finite values included. """ if (obsgeo is None or len(obsgeo) != 6 or np.all(np.array(obsgeo) == 0) or np.all(~np.isfinite(obsgeo))): # NOQA raise ValueError( f"Can not parse the 'obsgeo' location ({obsgeo}). " "obsgeo should be a length 6 non-zero, finite numpy array") # If the cartesian coords are zero or have NaNs in them use the spherical ones if np.all(obsgeo[:3] == 0) or np.any(~np.isfinite(obsgeo[:3])): data = SphericalRepresentation(*(obsgeo[3:] * (u.deg, u.deg, u.m))) # Otherwise we assume the cartesian ones are valid else: data = CartesianRepresentation(*obsgeo[:3] * u.m) return ITRS(data, obstime=obstime)
def test_atciqz_aticq(st): """Check replacements against erfa versions for consistency.""" t, pos = st jd1, jd2 = get_jd12(t, 'tdb') astrom, _ = erfa.apci13(jd1, jd2) ra, dec = pos srepr = SphericalRepresentation(ra, dec, 1) ra = ra.value dec = dec.value assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(srepr, astrom)) assert_allclose(erfa.aticq(ra, dec, astrom), aticq(srepr, astrom))
def hadec_to_radec(ha, dec, t, lon=WSRT_LOC.lat): """ Convert apparent HA, Dec to J2000 RA, Dec :param ha: hour angle with unit :param dec: declination with unit :param t: UT time (string or astropy.time.Time) :param lon: Longitude with unit (default: WSRT) :return: SkyCoord object of J2000 coordinates """ # Convert time to Time object if given as string if isinstance(t, str): t = Time(t) # create spherical representation of ITRS coordinates of given ha, dec itrs_spherical = SphericalRepresentation(lon - ha, dec, 1.) # create ITRS object, which requires cartesian input coord = SkyCoord(itrs_spherical.to_cartesian(), frame='itrs', obstime=t) # convert to J2000 return coord.icrs
def setup(self): lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays self.s0 = SphericalRepresentation( lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc) self.diff = SphericalDifferential( d_lon=np.ones(self.s0.shape)*u.mas/u.yr, d_lat=np.ones(self.s0.shape)*u.mas/u.yr, d_distance=np.ones(self.s0.shape)*u.km/u.s) self.s0 = self.s0.with_differentials(self.diff) # With unequal arrays -> these will be broadcasted. self.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, differentials=self.diff) # For completeness on some tests, also a cartesian one self.c0 = self.s0.to_cartesian()
def setup(self): lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays self.s0 = SphericalRepresentation( lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc) self.diff = SphericalDifferential( d_lon=np.ones(self.s0.shape) * u.mas / u.yr, d_lat=np.ones(self.s0.shape) * u.mas / u.yr, d_distance=np.ones(self.s0.shape) * u.km / u.s) self.s0 = self.s0.with_differentials(self.diff) # With unequal arrays -> these will be broadcasted. self.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, differentials=self.diff) # For completeness on some tests, also a cartesian one self.c0 = self.s0.to_cartesian()
def rotatedsun_to_reference(rotatedsun_coord, reference_frame): # Transform to HCI from_coord = rotatedsun_coord.base.realize_frame(rotatedsun_coord.data) hci_coord = from_coord.transform_to(HeliocentricInertial(obstime=reference_frame.obstime)) oldrepr = hci_coord.spherical # Rotate the coordinate in HCI from sunpy.physics.differential_rotation import diff_rot log.debug(f"Applying {rotatedsun_coord.duration} of solar rotation") newlon = oldrepr.lon + diff_rot(rotatedsun_coord.duration, oldrepr.lat, rot_type=rotatedsun_coord.rotation_model, frame_time='sidereal') newrepr = SphericalRepresentation(newlon, oldrepr.lat, oldrepr.distance) # Transform back from HCI hci_coord = HeliocentricInertial(newrepr, obstime=reference_frame.obstime) return hci_coord.transform_to(reference_frame)
def reference_to_rotatedsun(hgs_coord, rotatedsun_frame): int_frame = HeliographicStonyhurst( obstime=rotatedsun_frame.base.obstime) int_coord = hgs_coord.make_3d().transform_to( int_frame) # obstime change handled here oldrepr = int_coord.spherical # Rotate the coordinate in HGS from sunpy.physics.differential_rotation import diff_rot log.debug(f"Applying {rotatedsun_frame.duration} of solar rotation") newlon = oldrepr.lon - diff_rot( rotatedsun_frame.duration, oldrepr.lat, rot_type=rotatedsun_frame.rotation_model, frame_time='sidereal') newrepr = SphericalRepresentation(newlon, oldrepr.lat, oldrepr.distance) # Transform from HGS new_coord = int_coord.realize_frame(newrepr).transform_to( rotatedsun_frame.base) return rotatedsun_frame.realize_frame(new_coord.data)
0 * u.arcsec)], None), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], None), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], { 'obstime': '2011/01/01T00:00:00' })] """ These are common 3D params, kwargs are frame specific """ three_D_parameters = [ ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], None), ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], { 'obstime': '2011/01/01T00:00:00' }), ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], { 'representation': 'spherical' }), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], { 'obstime': '2011/01/01T00:00:00' }) ] # ============================================================================== # Helioprojective Tests # ============================================================================== @pytest.mark.parametrize('args, kwargs', two_D_parameters + [(None, { 'Tx': 0 * u.deg, 'Ty': 0 * u.arcsec
@pytest.mark.parametrize('frame', ['fk4', 'altaz']) def test_skycoord(frame): c = SkyCoord([[1, 2], [3, 4]], [[5, 6], [7, 8]], unit='deg', frame=frame, obstime=Time('2016-01-02'), location=EarthLocation(1000, 2000, 3000, unit=u.km)) cy = load(dump(c)) compare_coord(c, cy) @pytest.mark.parametrize('rep', [ CartesianRepresentation(1*u.m, 2.*u.m, 3.*u.m), SphericalRepresentation([[1, 2], [3, 4]]*u.deg, [[5, 6], [7, 8]]*u.deg, 10*u.pc), UnitSphericalRepresentation(0*u.deg, 10*u.deg), SphericalCosLatDifferential([[1.], [2.]]*u.mas/u.yr, [4., 5.]*u.mas/u.yr, [[[10]], [[20]]]*u.km/u.s), CartesianDifferential([10, 20, 30]*u.km/u.s), CartesianRepresentation( [1, 2, 3]*u.m, differentials=CartesianDifferential([10, 20, 30]*u.km/u.s)), SphericalRepresentation( [[1, 2], [3, 4]]*u.deg, [[5, 6], [7, 8]]*u.deg, 10*u.pc, differentials={ 's': SphericalDifferential([[0., 1.], [2., 3.]]*u.mas/u.yr, [[4., 5.], [6., 7.]]*u.mas/u.yr, 10*u.km/u.s)})])
# fix this. if attr == 'info.meta': if a1 is None: a1 = {} if a2 is None: a2 = {} assert np.all(a1 == a2) # Testing FITS table read/write with mixins. This is mostly # copied from ECSV mixin testing. el = EarthLocation(x=1 * u.km, y=3 * u.km, z=5 * u.km) el2 = EarthLocation(x=[1, 2] * u.km, y=[3, 4] * u.km, z=[5, 6] * u.km) sr = SphericalRepresentation([0, 1] * u.deg, [2, 3] * u.deg, 1 * u.kpc) cr = CartesianRepresentation([0, 1] * u.pc, [4, 5] * u.pc, [8, 6] * u.pc) sd = SphericalCosLatDifferential([0, 1] * u.mas / u.yr, [0, 1] * u.mas / u.yr, 10 * u.km / u.s) srd = SphericalRepresentation(sr, differentials=sd) sc = SkyCoord([1, 2], [3, 4], unit='deg,deg', frame='fk4', obstime='J1990.5') scc = sc.copy() scc.representation_type = 'cartesian' tm = Time([2450814.5, 2450815.5], format='jd', scale='tai', location=el) # NOTE: in the test below the name of the column "x" for the Quantity is # important since it tests the fix for #10215 (namespace clash, where "x" # clashes with "el2.x"). mixin_cols = { 'tm': tm,
class TestManipulation(): """Manipulation of Representation shapes. Checking that attributes are manipulated correctly. Even more exhaustive tests are done in time.tests.test_methods """ def setup(self): lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays self.s0 = SphericalRepresentation( lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc) self.diff = SphericalDifferential( d_lon=np.ones(self.s0.shape)*u.mas/u.yr, d_lat=np.ones(self.s0.shape)*u.mas/u.yr, d_distance=np.ones(self.s0.shape)*u.km/u.s) self.s0 = self.s0.with_differentials(self.diff) # With unequal arrays -> these will be broadcasted. self.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, differentials=self.diff) # For completeness on some tests, also a cartesian one self.c0 = self.s0.to_cartesian() def test_ravel(self): s0_ravel = self.s0.ravel() assert type(s0_ravel) is type(self.s0) assert s0_ravel.shape == (self.s0.size,) assert np.all(s0_ravel.lon == self.s0.lon.ravel()) assert np.may_share_memory(s0_ravel.lon, self.s0.lon) assert np.may_share_memory(s0_ravel.lat, self.s0.lat) assert np.may_share_memory(s0_ravel.distance, self.s0.distance) assert s0_ravel.differentials['s'].shape == (self.s0.size,) # Since s1 was broadcast, ravel needs to make a copy. s1_ravel = self.s1.ravel() assert type(s1_ravel) is type(self.s1) assert s1_ravel.shape == (self.s1.size,) assert s1_ravel.differentials['s'].shape == (self.s1.size,) assert np.all(s1_ravel.lon == self.s1.lon.ravel()) assert not np.may_share_memory(s1_ravel.lat, self.s1.lat) def test_copy(self): s0_copy = self.s0.copy() s0_copy_diff = s0_copy.differentials['s'] assert s0_copy.shape == self.s0.shape assert np.all(s0_copy.lon == self.s0.lon) assert np.all(s0_copy.lat == self.s0.lat) # Check copy was made of internal data. assert not np.may_share_memory(s0_copy.distance, self.s0.distance) assert not np.may_share_memory(s0_copy_diff.d_lon, self.diff.d_lon) def test_flatten(self): s0_flatten = self.s0.flatten() s0_diff = s0_flatten.differentials['s'] assert s0_flatten.shape == (self.s0.size,) assert s0_diff.shape == (self.s0.size,) assert np.all(s0_flatten.lon == self.s0.lon.flatten()) assert np.all(s0_diff.d_lon == self.diff.d_lon.flatten()) # Flatten always copies. assert not np.may_share_memory(s0_flatten.distance, self.s0.distance) assert not np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) s1_flatten = self.s1.flatten() assert s1_flatten.shape == (self.s1.size,) assert np.all(s1_flatten.lon == self.s1.lon.flatten()) assert not np.may_share_memory(s1_flatten.lat, self.s1.lat) def test_transpose(self): s0_transpose = self.s0.transpose() s0_diff = s0_transpose.differentials['s'] assert s0_transpose.shape == (7, 6) assert s0_diff.shape == s0_transpose.shape assert np.all(s0_transpose.lon == self.s0.lon.transpose()) assert np.all(s0_diff.d_lon == self.diff.d_lon.transpose()) assert np.may_share_memory(s0_transpose.distance, self.s0.distance) assert np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) s1_transpose = self.s1.transpose() s1_diff = s1_transpose.differentials['s'] assert s1_transpose.shape == (7, 6) assert s1_diff.shape == s1_transpose.shape assert np.all(s1_transpose.lat == self.s1.lat.transpose()) assert np.all(s1_diff.d_lon == self.diff.d_lon.transpose()) assert np.may_share_memory(s1_transpose.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) # Only one check on T, since it just calls transpose anyway. # Doing it on the CartesianRepresentation just for variety's sake. c0_T = self.c0.T assert c0_T.shape == (7, 6) assert np.all(c0_T.x == self.c0.x.T) assert np.may_share_memory(c0_T.y, self.c0.y) def test_diagonal(self): s0_diagonal = self.s0.diagonal() s0_diff = s0_diagonal.differentials['s'] assert s0_diagonal.shape == (6,) assert s0_diff.shape == s0_diagonal.shape assert np.all(s0_diagonal.lat == self.s0.lat.diagonal()) assert np.all(s0_diff.d_lon == self.diff.d_lon.diagonal()) assert np.may_share_memory(s0_diagonal.lat, self.s0.lat) assert np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) def test_swapaxes(self): s1_swapaxes = self.s1.swapaxes(0, 1) s1_diff = s1_swapaxes.differentials['s'] assert s1_swapaxes.shape == (7, 6) assert s1_diff.shape == s1_swapaxes.shape assert np.all(s1_swapaxes.lat == self.s1.lat.swapaxes(0, 1)) assert np.all(s1_diff.d_lon == self.diff.d_lon.swapaxes(0, 1)) assert np.may_share_memory(s1_swapaxes.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) def test_reshape(self): s0_reshape = self.s0.reshape(2, 3, 7) s0_diff = s0_reshape.differentials['s'] assert s0_reshape.shape == (2, 3, 7) assert s0_diff.shape == s0_reshape.shape assert np.all(s0_reshape.lon == self.s0.lon.reshape(2, 3, 7)) assert np.all(s0_reshape.lat == self.s0.lat.reshape(2, 3, 7)) assert np.all(s0_reshape.distance == self.s0.distance.reshape(2, 3, 7)) assert np.may_share_memory(s0_reshape.lon, self.s0.lon) assert np.may_share_memory(s0_reshape.lat, self.s0.lat) assert np.may_share_memory(s0_reshape.distance, self.s0.distance) s1_reshape = self.s1.reshape(3, 2, 7) s1_diff = s1_reshape.differentials['s'] assert s1_reshape.shape == (3, 2, 7) assert s1_diff.shape == s1_reshape.shape assert np.all(s1_reshape.lat == self.s1.lat.reshape(3, 2, 7)) assert np.all(s1_diff.d_lon == self.diff.d_lon.reshape(3, 2, 7)) assert np.may_share_memory(s1_reshape.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) # For reshape(3, 14), copying is necessary for lon, lat, but not for d s1_reshape2 = self.s1.reshape(3, 14) assert s1_reshape2.shape == (3, 14) assert np.all(s1_reshape2.lon == self.s1.lon.reshape(3, 14)) assert not np.may_share_memory(s1_reshape2.lon, self.s1.lon) assert s1_reshape2.distance.shape == (3, 14) assert np.may_share_memory(s1_reshape2.distance, self.s1.distance) def test_shape_setting(self): # Shape-setting should be on the object itself, since copying removes # zero-strides due to broadcasting. We reset the objects at the end. self.s0.shape = (2, 3, 7) assert self.s0.shape == (2, 3, 7) assert self.s0.lon.shape == (2, 3, 7) assert self.s0.lat.shape == (2, 3, 7) assert self.s0.distance.shape == (2, 3, 7) assert self.diff.shape == (2, 3, 7) assert self.diff.d_lon.shape == (2, 3, 7) assert self.diff.d_lat.shape == (2, 3, 7) assert self.diff.d_distance.shape == (2, 3, 7) # this works with the broadcasting. self.s1.shape = (2, 3, 7) assert self.s1.shape == (2, 3, 7) assert self.s1.lon.shape == (2, 3, 7) assert self.s1.lat.shape == (2, 3, 7) assert self.s1.distance.shape == (2, 3, 7) assert self.s1.distance.strides == (0, 0, 0) # but this one does not. oldshape = self.s1.shape with pytest.raises(AttributeError): self.s1.shape = (42,) assert self.s1.shape == oldshape assert self.s1.lon.shape == oldshape assert self.s1.lat.shape == oldshape assert self.s1.distance.shape == oldshape # Finally, a more complicated one that checks that things get reset # properly if it is not the first component that fails. s2 = SphericalRepresentation(self.s1.lon.copy(), self.s1.lat, self.s1.distance, copy=False) assert 0 not in s2.lon.strides assert 0 in s2.lat.strides with pytest.raises(AttributeError): s2.shape = (42,) assert s2.shape == oldshape assert s2.lon.shape == oldshape assert s2.lat.shape == oldshape assert s2.distance.shape == oldshape assert 0 not in s2.lon.strides assert 0 in s2.lat.strides self.setup() def test_squeeze(self): s0_squeeze = self.s0.reshape(3, 1, 2, 1, 7).squeeze() s0_diff = s0_squeeze.differentials['s'] assert s0_squeeze.shape == (3, 2, 7) assert s0_diff.shape == s0_squeeze.shape assert np.all(s0_squeeze.lat == self.s0.lat.reshape(3, 2, 7)) assert np.all(s0_diff.d_lon == self.diff.d_lon.reshape(3, 2, 7)) assert np.may_share_memory(s0_squeeze.lat, self.s0.lat) def test_add_dimension(self): s0_adddim = self.s0[:, np.newaxis, :] s0_diff = s0_adddim.differentials['s'] assert s0_adddim.shape == (6, 1, 7) assert s0_diff.shape == s0_adddim.shape assert np.all(s0_adddim.lon == self.s0.lon[:, np.newaxis, :]) assert np.all(s0_diff.d_lon == self.diff.d_lon[:, np.newaxis, :]) assert np.may_share_memory(s0_adddim.lat, self.s0.lat) def test_take(self): s0_take = self.s0.take((5, 2)) s0_diff = s0_take.differentials['s'] assert s0_take.shape == (2,) assert s0_diff.shape == s0_take.shape assert np.all(s0_take.lon == self.s0.lon.take((5, 2))) assert np.all(s0_diff.d_lon == self.diff.d_lon.take((5, 2))) def test_broadcast_to(self): s0_broadcast = self.s0._apply(np.broadcast_to, (3, 6, 7), subok=True) s0_diff = s0_broadcast.differentials['s'] assert type(s0_broadcast) is type(self.s0) assert s0_broadcast.shape == (3, 6, 7) assert s0_diff.shape == s0_broadcast.shape assert np.all(s0_broadcast.lon == self.s0.lon) assert np.all(s0_broadcast.lat == self.s0.lat) assert np.all(s0_broadcast.distance == self.s0.distance) assert np.may_share_memory(s0_broadcast.lon, self.s0.lon) assert np.may_share_memory(s0_broadcast.lat, self.s0.lat) assert np.may_share_memory(s0_broadcast.distance, self.s0.distance) s1_broadcast = self.s1._apply(np.broadcast_to, shape=(3, 6, 7), subok=True) s1_diff = s1_broadcast.differentials['s'] assert s1_broadcast.shape == (3, 6, 7) assert s1_diff.shape == s1_broadcast.shape assert np.all(s1_broadcast.lat == self.s1.lat) assert np.all(s1_broadcast.lon == self.s1.lon) assert np.all(s1_broadcast.distance == self.s1.distance) assert s1_broadcast.distance.shape == (3, 6, 7) assert np.may_share_memory(s1_broadcast.lat, self.s1.lat) assert np.may_share_memory(s1_broadcast.lon, self.s1.lon) assert np.may_share_memory(s1_broadcast.distance, self.s1.distance) # A final test that "may_share_memory" equals "does_share_memory" # Do this on a copy, to keep self.s0 unchanged. sc = self.s0.copy() assert not np.may_share_memory(sc.lon, self.s0.lon) assert not np.may_share_memory(sc.lat, self.s0.lat) sc_broadcast = sc._apply(np.broadcast_to, (3, 6, 7), subok=True) assert np.may_share_memory(sc_broadcast.lon, sc.lon) # Can only write to copy, not to broadcast version. sc.lon[0, 0] = 22. * u.hourangle assert np.all(sc_broadcast.lon[:, 0, 0] == 22. * u.hourangle)
# Licensed under a 3-clause BSD style license - see LICENSE.rst """Test replacements for ERFA functions atciqz and aticq.""" import pytest import erfa from astropy.tests.helper import assert_quantity_allclose as assert_allclose from astropy.time import Time import astropy.units as u from astropy.coordinates.builtin_frames.utils import get_jd12, atciqz, aticq from astropy.coordinates import SphericalRepresentation # Hard-coded random values sph = SphericalRepresentation(lon=[15., 214.] * u.deg, lat=[-12., 64.] * u.deg, distance=[1, 1.]) @pytest.mark.parametrize('t', [Time("2014-06-25T00:00"), Time(["2014-06-25T00:00", "2014-09-24"])]) @pytest.mark.parametrize('pos', [sph[0], sph]) def test_atciqz_aticq(t, pos): """Check replacements against erfa versions for consistency.""" jd1, jd2 = get_jd12(t, 'tdb') astrom, _ = erfa.apci13(jd1, jd2) ra = pos.lon.to_value(u.rad) dec = pos.lat.to_value(u.rad) assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(pos, astrom)) assert_allclose(erfa.aticq(ra, dec, astrom), aticq(pos, astrom))
def loc(self): sr = SphericalRepresentation(self.theta + self.phase0, self.inclination*np.cos(self.theta), self.r) return sr.to_cartesian() + self.parent_body.loc
def plot_star(self, fade_out=True): spots_spherical = SphericalRepresentation( self.phi * u.rad, (self.theta - np.pi / 2) * u.rad, 1 * R_sun) self.spots_spherical = spots_spherical fig, ax = plot_star(spots_spherical, fade_out=fade_out)
class TestManipulation(): """Manipulation of Representation shapes. Checking that attributes are manipulated correctly. Even more exhaustive tests are done in time.tests.test_methods """ def setup(self): lon = Longitude(np.arange(0, 24, 4), u.hourangle) lat = Latitude(np.arange(-90, 91, 30), u.deg) # With same-sized arrays self.s0 = SphericalRepresentation( lon[:, np.newaxis] * np.ones(lat.shape), lat * np.ones(lon.shape)[:, np.newaxis], np.ones(lon.shape + lat.shape) * u.kpc) self.diff = SphericalDifferential( d_lon=np.ones(self.s0.shape) * u.mas / u.yr, d_lat=np.ones(self.s0.shape) * u.mas / u.yr, d_distance=np.ones(self.s0.shape) * u.km / u.s) self.s0 = self.s0.with_differentials(self.diff) # With unequal arrays -> these will be broadcasted. self.s1 = SphericalRepresentation(lon[:, np.newaxis], lat, 1. * u.kpc, differentials=self.diff) # For completeness on some tests, also a cartesian one self.c0 = self.s0.to_cartesian() def test_ravel(self): s0_ravel = self.s0.ravel() assert type(s0_ravel) is type(self.s0) assert s0_ravel.shape == (self.s0.size, ) assert np.all(s0_ravel.lon == self.s0.lon.ravel()) assert np.may_share_memory(s0_ravel.lon, self.s0.lon) assert np.may_share_memory(s0_ravel.lat, self.s0.lat) assert np.may_share_memory(s0_ravel.distance, self.s0.distance) assert s0_ravel.differentials['s'].shape == (self.s0.size, ) # Since s1 was broadcast, ravel needs to make a copy. s1_ravel = self.s1.ravel() assert type(s1_ravel) is type(self.s1) assert s1_ravel.shape == (self.s1.size, ) assert s1_ravel.differentials['s'].shape == (self.s1.size, ) assert np.all(s1_ravel.lon == self.s1.lon.ravel()) assert not np.may_share_memory(s1_ravel.lat, self.s1.lat) def test_copy(self): s0_copy = self.s0.copy() s0_copy_diff = s0_copy.differentials['s'] assert s0_copy.shape == self.s0.shape assert np.all(s0_copy.lon == self.s0.lon) assert np.all(s0_copy.lat == self.s0.lat) # Check copy was made of internal data. assert not np.may_share_memory(s0_copy.distance, self.s0.distance) assert not np.may_share_memory(s0_copy_diff.d_lon, self.diff.d_lon) def test_flatten(self): s0_flatten = self.s0.flatten() s0_diff = s0_flatten.differentials['s'] assert s0_flatten.shape == (self.s0.size, ) assert s0_diff.shape == (self.s0.size, ) assert np.all(s0_flatten.lon == self.s0.lon.flatten()) assert np.all(s0_diff.d_lon == self.diff.d_lon.flatten()) # Flatten always copies. assert not np.may_share_memory(s0_flatten.distance, self.s0.distance) assert not np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) s1_flatten = self.s1.flatten() assert s1_flatten.shape == (self.s1.size, ) assert np.all(s1_flatten.lon == self.s1.lon.flatten()) assert not np.may_share_memory(s1_flatten.lat, self.s1.lat) def test_transpose(self): s0_transpose = self.s0.transpose() s0_diff = s0_transpose.differentials['s'] assert s0_transpose.shape == (7, 6) assert s0_diff.shape == s0_transpose.shape assert np.all(s0_transpose.lon == self.s0.lon.transpose()) assert np.all(s0_diff.d_lon == self.diff.d_lon.transpose()) assert np.may_share_memory(s0_transpose.distance, self.s0.distance) assert np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) s1_transpose = self.s1.transpose() s1_diff = s1_transpose.differentials['s'] assert s1_transpose.shape == (7, 6) assert s1_diff.shape == s1_transpose.shape assert np.all(s1_transpose.lat == self.s1.lat.transpose()) assert np.all(s1_diff.d_lon == self.diff.d_lon.transpose()) assert np.may_share_memory(s1_transpose.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) # Only one check on T, since it just calls transpose anyway. # Doing it on the CartesianRepresentation just for variety's sake. c0_T = self.c0.T assert c0_T.shape == (7, 6) assert np.all(c0_T.x == self.c0.x.T) assert np.may_share_memory(c0_T.y, self.c0.y) def test_diagonal(self): s0_diagonal = self.s0.diagonal() s0_diff = s0_diagonal.differentials['s'] assert s0_diagonal.shape == (6, ) assert s0_diff.shape == s0_diagonal.shape assert np.all(s0_diagonal.lat == self.s0.lat.diagonal()) assert np.all(s0_diff.d_lon == self.diff.d_lon.diagonal()) assert np.may_share_memory(s0_diagonal.lat, self.s0.lat) assert np.may_share_memory(s0_diff.d_lon, self.diff.d_lon) def test_swapaxes(self): s1_swapaxes = self.s1.swapaxes(0, 1) s1_diff = s1_swapaxes.differentials['s'] assert s1_swapaxes.shape == (7, 6) assert s1_diff.shape == s1_swapaxes.shape assert np.all(s1_swapaxes.lat == self.s1.lat.swapaxes(0, 1)) assert np.all(s1_diff.d_lon == self.diff.d_lon.swapaxes(0, 1)) assert np.may_share_memory(s1_swapaxes.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) def test_reshape(self): s0_reshape = self.s0.reshape(2, 3, 7) s0_diff = s0_reshape.differentials['s'] assert s0_reshape.shape == (2, 3, 7) assert s0_diff.shape == s0_reshape.shape assert np.all(s0_reshape.lon == self.s0.lon.reshape(2, 3, 7)) assert np.all(s0_reshape.lat == self.s0.lat.reshape(2, 3, 7)) assert np.all(s0_reshape.distance == self.s0.distance.reshape(2, 3, 7)) assert np.may_share_memory(s0_reshape.lon, self.s0.lon) assert np.may_share_memory(s0_reshape.lat, self.s0.lat) assert np.may_share_memory(s0_reshape.distance, self.s0.distance) s1_reshape = self.s1.reshape(3, 2, 7) s1_diff = s1_reshape.differentials['s'] assert s1_reshape.shape == (3, 2, 7) assert s1_diff.shape == s1_reshape.shape assert np.all(s1_reshape.lat == self.s1.lat.reshape(3, 2, 7)) assert np.all(s1_diff.d_lon == self.diff.d_lon.reshape(3, 2, 7)) assert np.may_share_memory(s1_reshape.lat, self.s1.lat) assert np.may_share_memory(s1_diff.d_lon, self.diff.d_lon) # For reshape(3, 14), copying is necessary for lon, lat, but not for d s1_reshape2 = self.s1.reshape(3, 14) assert s1_reshape2.shape == (3, 14) assert np.all(s1_reshape2.lon == self.s1.lon.reshape(3, 14)) assert not np.may_share_memory(s1_reshape2.lon, self.s1.lon) assert s1_reshape2.distance.shape == (3, 14) assert np.may_share_memory(s1_reshape2.distance, self.s1.distance) def test_shape_setting(self): # Shape-setting should be on the object itself, since copying removes # zero-strides due to broadcasting. We reset the objects at the end. self.s0.shape = (2, 3, 7) assert self.s0.shape == (2, 3, 7) assert self.s0.lon.shape == (2, 3, 7) assert self.s0.lat.shape == (2, 3, 7) assert self.s0.distance.shape == (2, 3, 7) assert self.diff.shape == (2, 3, 7) assert self.diff.d_lon.shape == (2, 3, 7) assert self.diff.d_lat.shape == (2, 3, 7) assert self.diff.d_distance.shape == (2, 3, 7) # this works with the broadcasting. self.s1.shape = (2, 3, 7) assert self.s1.shape == (2, 3, 7) assert self.s1.lon.shape == (2, 3, 7) assert self.s1.lat.shape == (2, 3, 7) assert self.s1.distance.shape == (2, 3, 7) assert self.s1.distance.strides == (0, 0, 0) # but this one does not. oldshape = self.s1.shape with pytest.raises(ValueError): self.s1.shape = (1, ) with pytest.raises(AttributeError): self.s1.shape = (42, ) assert self.s1.shape == oldshape assert self.s1.lon.shape == oldshape assert self.s1.lat.shape == oldshape assert self.s1.distance.shape == oldshape # Finally, a more complicated one that checks that things get reset # properly if it is not the first component that fails. s2 = SphericalRepresentation(self.s1.lon.copy(), self.s1.lat, self.s1.distance, copy=False) assert 0 not in s2.lon.strides assert 0 in s2.lat.strides with pytest.raises(AttributeError): s2.shape = (42, ) assert s2.shape == oldshape assert s2.lon.shape == oldshape assert s2.lat.shape == oldshape assert s2.distance.shape == oldshape assert 0 not in s2.lon.strides assert 0 in s2.lat.strides self.setup() def test_squeeze(self): s0_squeeze = self.s0.reshape(3, 1, 2, 1, 7).squeeze() s0_diff = s0_squeeze.differentials['s'] assert s0_squeeze.shape == (3, 2, 7) assert s0_diff.shape == s0_squeeze.shape assert np.all(s0_squeeze.lat == self.s0.lat.reshape(3, 2, 7)) assert np.all(s0_diff.d_lon == self.diff.d_lon.reshape(3, 2, 7)) assert np.may_share_memory(s0_squeeze.lat, self.s0.lat) def test_add_dimension(self): s0_adddim = self.s0[:, np.newaxis, :] s0_diff = s0_adddim.differentials['s'] assert s0_adddim.shape == (6, 1, 7) assert s0_diff.shape == s0_adddim.shape assert np.all(s0_adddim.lon == self.s0.lon[:, np.newaxis, :]) assert np.all(s0_diff.d_lon == self.diff.d_lon[:, np.newaxis, :]) assert np.may_share_memory(s0_adddim.lat, self.s0.lat) def test_take(self): s0_take = self.s0.take((5, 2)) s0_diff = s0_take.differentials['s'] assert s0_take.shape == (2, ) assert s0_diff.shape == s0_take.shape assert np.all(s0_take.lon == self.s0.lon.take((5, 2))) assert np.all(s0_diff.d_lon == self.diff.d_lon.take((5, 2))) def test_broadcast_to(self): s0_broadcast = self.s0._apply(np.broadcast_to, (3, 6, 7), subok=True) s0_diff = s0_broadcast.differentials['s'] assert type(s0_broadcast) is type(self.s0) assert s0_broadcast.shape == (3, 6, 7) assert s0_diff.shape == s0_broadcast.shape assert np.all(s0_broadcast.lon == self.s0.lon) assert np.all(s0_broadcast.lat == self.s0.lat) assert np.all(s0_broadcast.distance == self.s0.distance) assert np.may_share_memory(s0_broadcast.lon, self.s0.lon) assert np.may_share_memory(s0_broadcast.lat, self.s0.lat) assert np.may_share_memory(s0_broadcast.distance, self.s0.distance) s1_broadcast = self.s1._apply(np.broadcast_to, shape=(3, 6, 7), subok=True) s1_diff = s1_broadcast.differentials['s'] assert s1_broadcast.shape == (3, 6, 7) assert s1_diff.shape == s1_broadcast.shape assert np.all(s1_broadcast.lat == self.s1.lat) assert np.all(s1_broadcast.lon == self.s1.lon) assert np.all(s1_broadcast.distance == self.s1.distance) assert s1_broadcast.distance.shape == (3, 6, 7) assert np.may_share_memory(s1_broadcast.lat, self.s1.lat) assert np.may_share_memory(s1_broadcast.lon, self.s1.lon) assert np.may_share_memory(s1_broadcast.distance, self.s1.distance) # A final test that "may_share_memory" equals "does_share_memory" # Do this on a copy, to keep self.s0 unchanged. sc = self.s0.copy() assert not np.may_share_memory(sc.lon, self.s0.lon) assert not np.may_share_memory(sc.lat, self.s0.lat) sc_broadcast = sc._apply(np.broadcast_to, (3, 6, 7), subok=True) assert np.may_share_memory(sc_broadcast.lon, sc.lon) # Can only write to copy, not to broadcast version. sc.lon[0, 0] = 22. * u.hourangle assert np.all(sc_broadcast.lon[:, 0, 0] == 22. * u.hourangle)
def plot_star(spots_spherical, fade_out=False): """ Parameters ---------- spots_spherical : `~astropy.coordinates.SphericalRepresentation` Points in spherical coordinates that represent the positions of the star spots. """ oldrcparams = matplotlib.rcParams matplotlib.rcParams['font.size'] = 18 fig, ax = plt.subplots(2, 3, figsize=(16, 16)) positive_x = ax[0, 0] negative_x = ax[1, 0] positive_y = ax[0, 1] negative_y = ax[1, 1] positive_z = ax[0, 2] negative_z = ax[1, 2] axes = [ positive_z, positive_x, negative_z, negative_x, positive_y, negative_y ] axes_labels = ['+z', '+x', '-z', '-x', '+y', '-y'] # Set black background plot_props = dict(xlim=(-1, 1), ylim=(-1, 1), xticks=(), yticks=()) drange = np.linspace(-1, 1, 100) y = np.sqrt(1 - drange**2) bg_color = 'k' for axis in axes: axis.set(xticks=(), yticks=()) axis.fill_between(drange, y, 1, color=bg_color) axis.fill_between(drange, -1, -y, color=bg_color) axis.set(**plot_props) axis.set_aspect('equal') # Set labels positive_x.set(xlabel='$\hat{z}$', ylabel='$\hat{y}$') # title='+x', positive_x.xaxis.set_label_position("top") positive_x.yaxis.set_label_position("right") negative_x.set(xlabel='$\hat{z}$', ylabel='$\hat{y}$') # title='-x', negative_x.xaxis.set_label_position("top") positive_y.set(xlabel='$\hat{z}$', ylabel='$\hat{x}$') positive_y.xaxis.set_label_position("top") negative_y.set(xlabel='$\hat{z}$', ylabel='$\hat{x}$') negative_y.xaxis.set_label_position("top") negative_y.yaxis.set_label_position("right") negative_z.set(xlabel='$\hat{y}$', ylabel='$\hat{x}$') # title='-z', negative_z.yaxis.set_label_position("right") positive_z.set(xlabel='$\hat{y}$', ylabel='$\hat{x}$') # title='+z', positive_z.yaxis.set_label_position("right") positive_z.xaxis.set_label_position("top") for axis, label in zip(axes, axes_labels): axis.annotate(label, (-0.9, 0.9), color='w', fontsize=14, ha='center', va='center') # Plot gridlines n_gridlines = 9 print("theta grid spacing: {0} deg".format(180. / (n_gridlines - 1))) n_points = 35 pi = np.pi thetaitude_lines = SphericalRepresentation( np.linspace(0, 2 * pi, n_points)[:, np.newaxis] * u.rad, np.linspace(-pi / 2, pi / 2, n_gridlines).T * u.rad, np.ones((n_points, 1))).to_cartesian() phigitude_lines = SphericalRepresentation( np.linspace(0, 2 * pi, n_gridlines)[:, np.newaxis] * u.rad, np.linspace(-pi / 2, pi / 2, n_points).T * u.rad, np.ones((n_gridlines, 1))).to_cartesian() for i in range(thetaitude_lines.shape[1]): for axis in [positive_z, negative_z]: axis.plot(thetaitude_lines.x[:, i], thetaitude_lines.y[:, i], ls=':', color='silver') for axis in [positive_x, negative_x, positive_y, negative_y]: axis.plot(thetaitude_lines.y[:, i], thetaitude_lines.z[:, i], ls=':', color='silver') for i in range(phigitude_lines.shape[0]): for axis in [positive_z, negative_z]: axis.plot(phigitude_lines.y[i, :], phigitude_lines.x[i, :], ls=':', color='silver') for axis in [positive_x, negative_x, positive_y, negative_y]: axis.plot(phigitude_lines.y[i, :], phigitude_lines.z[i, :], ls=':', color='silver') # Plot spots spots_cart = spots_spherical.to_cartesian() spots_x = spots_cart.x / R_sun spots_y = spots_cart.y / R_sun spots_z = spots_cart.z / R_sun if fade_out: n = float(len(spots_x)) alpha_range = np.arange(n) alpha = (n - alpha_range) / n else: alpha = 0.5 for spot_ind in range(spots_x.shape[1]): above_x_plane = spots_x[:, spot_ind] > 0 above_y_plane = spots_y[:, spot_ind] > 0 above_z_plane = spots_z[:, spot_ind] > 0 below_x_plane = spots_x[:, spot_ind] < 0 below_y_plane = spots_y[:, spot_ind] < 0 below_z_plane = spots_z[:, spot_ind] < 0 positive_x.plot(spots_y[above_x_plane, spot_ind], spots_z[above_x_plane, spot_ind], '.', alpha=alpha) negative_x.plot(-spots_y[below_x_plane, spot_ind], spots_z[below_x_plane, spot_ind], '.', alpha=alpha) positive_y.plot(-spots_x[above_y_plane, spot_ind], spots_z[above_y_plane, spot_ind], '.', alpha=alpha) negative_y.plot(spots_x[below_y_plane, spot_ind], spots_z[below_y_plane, spot_ind], '.', alpha=alpha) positive_z.plot(spots_x[above_z_plane, spot_ind], spots_y[above_z_plane, spot_ind], '.', alpha=alpha) negative_z.plot(spots_x[below_z_plane, spot_ind], -spots_y[below_z_plane, spot_ind], '.', alpha=alpha) matplotlib.rcParams = oldrcparams return fig, ax
class Star: mass = AffectsOmegaCrit('mass', u.kg) radius = AffectsOmegaCrit('radius', u.m) beta = ResetsGrid('beta') distortion = ResetsGrid('distortion') @u.quantity_input(mass=u.kg) @u.quantity_input(radius=u.m) @u.quantity_input(period=u.s) def __init__(self, mass, radius, period, beta=0.08, ulimb=0.9, ntiles=3072, distortion=True): self.distortion = distortion self.mass = mass self.radius = radius # will also set self.omega_crit self.beta = beta self.ulimb = ulimb self.ntiles = ntiles self.period = period self.clear_grid() def clear_grid(self): # now set up tile locations, velocities and directions. # These will be (nlon, nlat) CartesianRepresentations self.tile_locs = None self.tile_dirs = None self.tile_velocities = None # and arrays of tile properties - shape (nlon, nlat) self.tile_areas = None self.tile_fluxes = None # the next array is the main one that gets tweaked self.tile_scales = None """ We define many of the attributes as properties, so we can wipe the grid when they are set, and also check for violation of critical rotation """ @property def omega(self): """ Ratio of angular velocity to critical number. read-only property """ return (self.Omega/self.omega_crit).decompose() @property def period(self): return 2.0*np.pi/self.Omega @period.setter @u.quantity_input(value=u.s) def period(self, value): Omega = 2.0*np.pi/value if (Omega/self.omega_crit).decompose() > 1: raise ValueError('This rotation period exceeds critical value') self.Omega = Omega self.clear_grid() """ ntiles is also a property, since we need to remap to an appropriate value for HEALPIX. """ @property def ntiles(self): """ Number of tiles. Is checked to see if appropriate for HEALPIX algorithm. """ return self._ntiles @ntiles.setter def ntiles(self, value): allowed_values = [48, 192, 768, 3072, 12288, 49152, 196608] if int(value) not in allowed_values: raise ValueError('{} not one of allowed values: {!r}'.format( value, allowed_values )) self._ntiles = int(12*np.floor(np.sqrt(value/12.))**2) self.clear_grid() @u.quantity_input(wavelength=u.nm) def setup_grid(self, wavelength=656*u.nm): # use HEALPIX to get evenly sized tiles NSIDE = hp.npix2nside(self.ntiles) colat, lon = hp.pix2ang(NSIDE, np.arange(0, self.ntiles)) # co-latitude theta_values = u.Quantity(colat, unit=u.rad) # longitude phi_values = u.Quantity(lon, unit=u.rad) # the following formulae use the Roche approximation and assume # solid body rotation # solve for radius of rotating star at these co-latitudes if self.distortion: radii = self.radius*np.array([newton(surface, 1.01, args=(self.omega, x)) for x in theta_values]) else: radii = self.radius*np.ones(self.ntiles) # and effective gravities geff = np.sqrt((-const.G*self.mass/radii**2 + self.Omega**2 * radii * np.sin(theta_values)**2)**2 + self.Omega**4 * radii**2 * np.sin(theta_values)**2 * np.cos(theta_values)**2) # now make a ntiles sized CartesianRepresentation of positions self.tile_locs = SphericalRepresentation(phi_values, 90*u.deg-theta_values, radii).to_cartesian() # normal to tile is the direction of the derivate of the potential # this is the vector form of geff above # the easiest way to express it is that it differs from (r, theta, phi) # by a small amount in the theta direction epsilon x = radii/self.radius a = 1./x**2 - (8./27.)*self.omega**2 * x * np.sin(theta_values)**2 b = np.sqrt( (-1./x**2 + (8./27)*self.omega**2 * x * np.sin(theta_values)**2)**2 + ((8./27)*self.omega**2 * x * np.sin(theta_values) * np.cos(theta_values))**2 ) epsilon = np.arccos(a/b) self.tile_dirs = UnitSphericalRepresentation(phi_values, 90*u.deg - theta_values - epsilon) self.tile_dirs = self.tile_dirs.to_cartesian() # and ntiles sized arrays of tile properties tile_temperatures = 2000.0 * u.K * (geff / geff.max())**self.beta # fluxes, not accounting for limb darkening self.tile_scales = np.ones(self.ntiles) self.tile_fluxes = blackbody_nu(wavelength, tile_temperatures) # tile areas spher = self.tile_locs.represent_as(SphericalRepresentation) self.tile_areas = spher.distance**2 * hp.nside2pixarea(NSIDE) * u.rad * u.rad omega_vec = CartesianRepresentation( u.Quantity([0.0, 0.0, self.Omega.value], unit=self.Omega.unit) ) # get velocities of tiles self.tile_velocities = cross(omega_vec, self.tile_locs) @u.quantity_input(inclination=u.deg) def plot(self, inclination=90*u.deg, phase=0.0, savefig=False, filename='star_surface.png', cmap='magma', what='fluxes', cstride=1, rstride=1, shade=False): ax = plt.axes(projection='3d') ax.view_init(90-inclination.to(u.deg).value, 360*phase) # get map values if what == 'fluxes': vals = self.tile_fluxes * self.tile_scales vals = vals / vals.max() elif what == 'vels': earth = set_earth(inclination.to(u.deg).value, phase) velocities = self.tile_velocities.xyz vals = dot(earth, velocities).to(u.km/u.s) # can't plot negative values, so rescale from 0 - 1 vals = (vals - vals.min())/(vals.max()-vals.min()) elif what == 'areas': vals = self.tile_areas / self.tile_areas.max() colors = getattr(cm, cmap)(vals.value) # project the map to a rectangular matrix nlat = nlon = int(np.floor(np.sqrt(self.ntiles))) theta = np.linspace(np.pi, 0, nlat) phi = np.linspace(-np.pi, np.pi, nlon) PHI, THETA = np.meshgrid(phi, theta) NSIDE = hp.npix2nside(self.ntiles) grid_pix = hp.ang2pix(NSIDE, THETA, PHI) grid_map = colors[grid_pix] # Create a sphere r = 0.3 x = r*np.sin(THETA)*np.cos(PHI) y = r*np.sin(THETA)*np.sin(PHI) z = r*np.cos(THETA) ax.plot_surface(x, y, z, cstride=cstride, rstride=rstride, facecolors=grid_map, shade=shade) if savefig: plt.savefig(filename) else: plt.show() @u.quantity_input(inclination=u.deg) def view(self, inclination=90*u.deg, phase=0.0, what='fluxes', projection='mollweide', cmap='magma', savefig=False, filename='star_surface.png', dlat=30, dlon=30, **kwargs): rot = (360*phase, 90-inclination.to(u.deg).value, 0) if what == 'fluxes': vals = self.tile_fluxes * self.tile_scales vals = vals / vals.max() elif what == 'areas': vals = self.tile_areas / self.tile_areas.max() if 'mollweide'.find(projection) == 0: hp.mollview(vals, rot=rot, cmap=cmap, **kwargs) elif 'cartesian'.find(projection) == 0: hp.cartview(vals, rot=rot, cmap=cmap, **kwargs) elif 'orthographic'.find(projection) == 0: hp.orthview(vals, rot=rot, cmap=cmap, **kwargs) else: raise ValueError('Unrecognised projection') hp.graticule(dlat, dlon) if savefig: plt.savefig(filename) else: plt.show() @u.quantity_input(inclination=u.deg) def _luminosity_array(self, phase, inclination): if self.tile_locs is None: self.setup_grid() # get CartesianRepresentation pointing to earth at these phases earth = set_earth(inclination, phase) mu = dot(earth, self.tile_dirs, normalise=True) # mask of visible elements mask = mu >= 0.0 # broadcast and calculate phase = np.asarray(phase) new_shape = phase.shape + self.tile_fluxes.shape assert(new_shape == mu.shape), "Broadcasting has gone wrong" fluxes = np.tile(self.tile_fluxes, phase.size).reshape(new_shape) scales = np.tile(self.tile_scales, phase.size).reshape(new_shape) areas = np.tile(self.tile_areas, phase.size).reshape(new_shape) # limb darkened sum of all tile fluxes lum = (fluxes * scales * (1.0 - self.ulimb + np.fabs(mu)*self.ulimb) * areas * mu) # no contribution from invisible tiles lum[mask] = 0.0 return lum @u.quantity_input(inclination=u.deg) def calc_luminosity(self, phase, inclination): lum = self._luminosity_array(phase, inclination) return np.sum(lum, axis=1) @u.quantity_input(inclination=u.deg) @u.quantity_input(v_macro=u.km/u.s) @u.quantity_input(v_inst=u.km/u.s) @u.quantity_input(v_min=u.km/u.s) @u.quantity_input(v_max=u.km/u.s) def calc_line_profile(self, phase, inclination, nbins=100, v_macro=2*u.km/u.s, v_inst=4*u.km/u.s, v_min=-40*u.km/u.s, v_max=40*u.km/u.s): # get CartesianRepresentation pointing to earth at these phases earth = set_earth(inclination, phase) # get CartesianRepresentation of projected velocities vproj = dot(earth, self.tile_velocities).to(u.km/u.s) # which tiles fall in which bin? bins = np.linspace(v_min, v_max, nbins) indices = np.digitize(vproj, bins) lum = self._luminosity_array(phase, inclination) phase = np.asarray(phase) trailed_spectrum = np.zeros((phase.size, nbins)) for i in range(nbins): mask = (indices == i) trailed_spectrum[:, i] = np.sum(lum*mask, axis=1) # convolve with instrumental and local line profiles # TODO: realistic Line Profile Treatment # For now we assume every element has same intrinsic # line profile bin_width = (v_max-v_min)/(nbins-1) profile_width_in_bins = np.sqrt(v_macro**2 + v_inst**2) / bin_width gauss_kernel = Gaussian1DKernel(stddev=profile_width_in_bins, mode='linear_interp') for i in range(phase.size): trailed_spectrum[i, :] = convolve(trailed_spectrum[i, :], gauss_kernel, boundary='extend') return bins, trailed_spectrum