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()
Example #2
0
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()
Example #4
0
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)
Example #6
0
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)
Example #7
0
    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)
Example #8
0
    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()
Example #10
0
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
Example #11
0
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
Example #12
0
File: util.py Project: TRASAL/darc
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
Example #13
0
    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))
Example #15
0
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()
Example #18
0
    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)
Example #19
0
    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)
Example #20
0
                                                  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
Example #21
0
@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)})])
Example #22
0
        # 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)
Example #24
0
# 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))
Example #25
0
 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
Example #26
0
 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)
Example #28
0
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
Example #29
0
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