In [4]:
import astropy.coordinates as coord
import astropy.table as at
import astropy.units as u
import gala.coordinates as gc
import healpy as hp
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from healpy.projaxes import HpxMollweideAxes
import pyia

%matplotlib inline

Load DESI MWS DR1 data and positional cross-match to Gaia DR3:

In [2]:
desi = at.Table.read("~/data/DESI/DR1/mwsall-pix-iron.fits")
gaia = at.Table.read("~/data/DESI/DR1/desi_dr1_gaia_dr3_xm.fits")
len(desi), len(gaia)
WARNING: hdu= was not specified but multiple tables are present, reading in first available table (hdu=1) [astropy.io.fits.connect]
WARNING: UnitsWarning: 'log(cm.s**-2)' did not parse as fits unit: 'log' is not a recognized function If this is meant to be a custom unit, define it with 'u.def_unit'. To have it recognized inside a file reader or other code, enable it with 'u.add_enabled_units'. For details, see https://docs.astropy.org/en/latest/units/combining_and_defining.html [astropy.units.core]
WARNING: UnitsWarning: 'log(cm.s**-2)' did not parse as fits unit: 'log' is not a recognized function If this is meant to be a custom unit, define it with 'u.def_unit'. To have it recognized inside a file reader or other code, enable it with 'u.add_enabled_units'. For details, see https://docs.astropy.org/en/latest/units/combining_and_defining.html [astropy.units.core]
WARNING: UnitsWarning: 'log(cm.s**-2)' did not parse as fits unit: 'log' is not a recognized function If this is meant to be a custom unit, define it with 'u.def_unit'. To have it recognized inside a file reader or other code, enable it with 'u.add_enabled_units'. For details, see https://docs.astropy.org/en/latest/units/combining_and_defining.html [astropy.units.core]
WARNING: UnitsWarning: 'log(cm.s**-2)' did not parse as fits unit: 'log' is not a recognized function If this is meant to be a custom unit, define it with 'u.def_unit'. To have it recognized inside a file reader or other code, enable it with 'u.add_enabled_units'. For details, see https://docs.astropy.org/en/latest/units/combining_and_defining.html [astropy.units.core]
Out[2]:
(6372607, 5381083)

There are some duplicates in the cross-match -- either because a source was observed twice in DESI, or because there are multiple Gaia sources within the DESI fiber footprint:

In [3]:
unq, counts = np.unique(gaia["targetid"], return_counts=True)
duplicate_targetids = unq[counts > 1]
(counts > 1).sum()
Out[3]:
np.int64(212471)

For now, we'll ignore the duplicates:

In [6]:
joined = at.join(
    desi[~np.isin(desi["TARGETID"], duplicate_targetids)],
    gaia,
    keys_left="TARGETID",
    keys_right="targetid",
    join_type="inner",
)
len(joined), (counts == 1).sum()
Out[6]:
(4940396, np.int64(4940376))

Since we are working with Gaia data, we can use the pyia package to help:

In [7]:
g = pyia.GaiaData(
    joined, radial_velocity_colname="VRAD", radial_velocity_error_colname="VRAD_ERR"
)

Sky plots:¶

First make some visualizations of the DESI data on the sky (using healpy):

In [8]:
c = g.get_skycoord(distance=False)
In [9]:
def apw_healpix_plot(
    c: coord.SkyCoord,
    nside: int = 128,
    figsize: tuple[int, int] = (12, 6),
    projmap_kwargs: dict | None = None,
    graticule: bool = True,
):
    hpix = hp.ang2pix(nside, c.data.lon.degree, c.data.lat.degree, lonlat=True)
    hpix_counts = np.bincount(hpix, minlength=hp.nside2npix(nside))

    projmap_kwargs = projmap_kwargs or dict()
    projmap_kwargs.setdefault("vmin", 1.0)
    projmap_kwargs.setdefault("vmax", np.nanmax(hpix_counts))
    projmap_kwargs.setdefault("cmap", "magma_r")
    projmap_kwargs.setdefault("norm", mpl.colors.LogNorm())
    projmap_kwargs.setdefault("badcolor", "w")

    fig = plt.figure(figsize=figsize)
    proj_ax = HpxMollweideAxes(fig, 111)
    proj_ax.projmap(hpix_counts, **projmap_kwargs)

    if graticule:
        proj_ax.graticule(30.0, 30.0, color="tab:blue", alpha=0.75)
    fig.add_axes(proj_ax)

    return fig
In [11]:
fig = apw_healpix_plot(
    c,
    projmap_kwargs=dict(
        rot=[180, 0, 0],
    ),
)
fig.suptitle("ICRS centered on RA=180º");
No description has been provided for this image

Now we can look at the sky coverage in different coordinate systems, like Galactic, and a few different stellar stream coordinate frames:

In [ ]:
for frame in [
    coord.Galactic(),
    gc.SagittariusVasiliev21(),
    gc.GD1Koposov10(),
    gc.Pal5PriceWhelan18()
]:
    c_fr = c.transform_to(frame)
    fig = apw_healpix_plot(c_fr)
    fig.suptitle(frame.__class__.__name__)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Most of the DESI data does not cover my favorite streams, but it seems like Pal 5 may be in the footprint! LEt's look for some serendipitously-observed stars?

Pal 5¶

To get a sense of the footprint, I load data for RR Lyrae stars in the Pal 5 stream (from Price-Whelan et al. 2019):

In [13]:
rrl_pal5_g = pyia.GaiaData("~/projects/pal5-rrl/data/RRL-with-prob.fits")
rrl_pal5_g = rrl_pal5_g[
    (rrl_pal5_g.member_prob > 0.8) & (rrl_pal5_g.inside_stream_track)
]
c_rrl_pal5 = rrl_pal5_g.get_skycoord(distance=False).transform_to(
    gc.Pal5PriceWhelan18()
)
len(rrl_pal5_g)
Out[13]:
24
In [14]:
c_pal5 = c.transform_to(gc.Pal5PriceWhelan18())

Some rough selections to get stream stars:

In [15]:
mask = (
    (np.abs(c_pal5.phi1.wrap_at(180 * u.deg)) < 25 * u.deg)
    & (c_pal5.phi2 > -2 * u.deg)
    & (c_pal5.phi2 < 5 * u.deg)
    & (np.abs(c.pm_ra_cosdec.value - -2.75) < 1.5)
    & (np.abs(c.pm_dec.value - -2.69) < 1.5)
    & (g.FEH < -0.5)
)
In [16]:
plt.hist2d(
    g.bp_rp[mask].value,
    g.phot_g_mean_mag[mask].value,
    bins=(np.linspace(-0.5, 3.5, 128), np.linspace(12, 21, 128))
);
No description has been provided for this image
In [17]:
c_pal5_masked = c_pal5[mask]
len(c_pal5_masked)
Out[17]:
8705
In [18]:
plt.figure(figsize=(10, 5))
plt.plot(c_pal5_masked.phi1.degree, c_pal5_masked.phi2.degree, ls="none", alpha=0.5)
plt.scatter(c_rrl_pal5.phi1.degree, c_rrl_pal5.phi2.degree, color="tab:blue")
plt.xlim(-25, 25)
plt.ylim(-2, 5)
Out[18]:
(-2.0, 5.0)
No description has been provided for this image
In [19]:
plt.figure(figsize=(10, 5))
plt.plot(
    c_pal5_masked.phi1.degree, c_pal5_masked.radial_velocity.value, ls="none", alpha=0.5
)
plt.ylim(-150, 50)
Out[19]:
(-150.0, 50.0)
No description has been provided for this image
In [ ]: