Tutorials ========= In this tutorial, we'll walk through an example where we download a HARPS E2DS echelle spectrum of LkCa 4, and Proxima Centauri. We'll also download two spectral templates -- one for TiO and another for water. We'll then take the cross-correlation of the TiO template with the stellar spectra to show that there is significant TiO absorption in the atmosphere of Proxima, an M5V star, and more interestingly, that there is also TiO absorption in the atmosphere of LkCa 4, a "K7" star. Lastly, we'll hunt for cool starspots on the stellar surface by searching for cooler water absorption in the optical high resolution spectrum via the cross-correlation. Loading a spectrum ------------------ First, we need a spectrum to work with, so we'll download a HARPS E2DS spectrum of LkCa 4 and Proxima Centauri that's currently hosted on Google Drive using astropy's `~astropy.utils.data.download_file` function, like so: .. code-block:: python from astropy.utils.data import download_file lkca4_spectrum_url = 'https://drive.google.com/uc?export=download&id=1x3nIg1P5tYFQqJrwEpQU11XQOs3ImH3v' lkca4_spectrum_path = download_file(lkca4_spectrum_url) proxima_spectrum_url = 'https://drive.google.com/uc?export=download&id=1I7E1x1XRjcxXNQXiuaajb_Jz7Wn2N_Eo' proxima_spectrum_path = download_file(proxima_spectrum_url) (you could alternatively use any E2DS spectrum downloaded from the `ESO Archive `_). We now have a local copy of the spectrum of Proxima Cen. We can load this file into an `~hipparchus.EchelleSpectrum` object like so: .. code-block:: python from hipparchus import EchelleSpectrum lkca4_spectrum = EchelleSpectrum.from_e2ds(lkca4_spectrum_path) proxima_spectrum = EchelleSpectrum.from_e2ds(proxima_spectrum_path) proxima_spectrum.plot() .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt proxima_spectrum_url = 'https://drive.google.com/uc?export=download&id=1I7E1x1XRjcxXNQXiuaajb_Jz7Wn2N_Eo' proxima_spectrum_path = download_file(proxima_spectrum_url, cache=True, show_progress=False) from hipparchus import EchelleSpectrum proxima_spectrum = EchelleSpectrum.from_e2ds(proxima_spectrum_path) proxima_spectrum.plot() plt.xlabel('Wavelength [$\AA$]') plt.ylabel('Counts') Continuum normalize ------------------- Next we might want to continuum normalize each echelle order. We can do that simply with the `~hipparchus.EchelleSpectrum.continuum_normalize` command: .. code-block:: python lkca4_spectrum.continuum_normalize() proxima_spectrum.continuum_normalize() proxima_spectrum.plot() .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt proxima_spectrum_url = 'https://drive.google.com/uc?export=download&id=1I7E1x1XRjcxXNQXiuaajb_Jz7Wn2N_Eo' proxima_spectrum_path = download_file(proxima_spectrum_url, cache=True, show_progress=False) from hipparchus import EchelleSpectrum spectrum = EchelleSpectrum.from_e2ds(proxima_spectrum_path) spectrum.continuum_normalize() spectrum.plot() plt.xlabel('Wavelength [$\AA$]') plt.ylabel('Normalized Flux') plt.ylim([0, 1.5]) You can see that the continuum flux is now mostly normalized near unity. Our spectrum is now ready for cross-correlation business! Loading a spectral template --------------------------- Next we need to load spectral templates which we will cross-correlate with the observed spectrum of Proxima Centauri. We'll be loading emission spectra generated by Daniel Kitzmann which represent: TiO at 3000 K (roughly the effective temperature of Proxima Cen), and H2O at 2500 K (roughly the temperature of hypothesized starspots on Proxima Cen). First we download the spectral templates: .. code-block:: python from astropy.utils.data import download_file template_2500_h2o_url = 'https://drive.google.com/uc?export=download&id=1RIXBl3L3J_R9PQ-k_0BqAtO-9zYn2mag' template_3000_tio_url = 'https://drive.google.com/uc?export=download&id=1eGUBfk7Q9zaXgJQJtVFB6pit7cmoGCpn' template_2500_h2o_path = download_file(template_2500_h2o_url) template_3000_tio_path = download_file(template_3000_tio_url) We now have a local copy of the spectral templates for TiO and water. Let's load those templates into the `~hipparchus.Template` object: .. code-block:: python from hipparchus import Template template_2500_h2o = Template.from_npy(template_2500_h2o_path) template_3000_tio = Template.from_npy(template_3000_tio_path) template_3000_tio.plot() .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt from hipparchus import Template template_3000_tio_url = 'https://drive.google.com/uc?export=download&id=1eGUBfk7Q9zaXgJQJtVFB6pit7cmoGCpn' template_3000_tio_path = download_file(template_3000_tio_url, cache=True) template_3000_tio = Template.from_npy(template_3000_tio_path) template_3000_tio.plot() plt.xlabel('Wavelength [$\AA$]') plt.ylabel('Template Weighting') By default the template spectrum is normalized such that it is near zero in the continuum, and greater than zero in absorption lines. This allows one to easily compute the cross-correlation via the weighted mean. Cross correlation ----------------- So we have spectra, and a template. Now let's take the cross-correlation of the two! We define the cross-correlation function (CCF) for an observed spectrum :math:`x`, given a template spectrum evaluated at a specific velocity :math:`T(v)`, .. math:: \mathrm{CCF} = \sum_i x_i T_i(v), where we have normalized the template such that it is positive in molecular absorption features and near-zero in the continuum, and .. math:: \sum_i T_i(v) = 1. This definition of the CCF is straightforward to interpret: the CCF is a mean of the flux in each echelle order weighted by the values of the spectral template. When the velocity :math:`v` is incorrect and/or the template does not match the observed spectrum, the weighted-mean flux is near unity (continuum). When the velocity is correct and the template matches absorption features in the observed spectrum, the absorption features in the spectrum "align" with the inverse absorption features in the template, and the weighted-mean flux is less than one. In this way, the CCF yields a "mean absorption line" due to the molecule specified by the template at the velocity of the star. Let's compute the CCF between the template and the observed Proxima Cen spectrum in the echelle order nearest to the wavelength 6800 Angstroms using the `~hipparchus.cross_corr` function: .. code-block:: python from hipparchus import cross_cor ccf = cross_corr(proxima_spectrum.nearest_order(6800), template_3000_tio) ccf.plot() .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt from hipparchus import Template, EchelleSpectrum, cross_corr proxima_spectrum_url = 'https://drive.google.com/uc?export=download&id=1I7E1x1XRjcxXNQXiuaajb_Jz7Wn2N_Eo' proxima_spectrum_path = download_file(proxima_spectrum_url, cache=True, show_progress=False) spectrum = EchelleSpectrum.from_e2ds(proxima_spectrum_path) template_3000_tio_url = 'https://drive.google.com/uc?export=download&id=1eGUBfk7Q9zaXgJQJtVFB6pit7cmoGCpn' template_3000_tio_path = download_file(template_3000_tio_url, cache=True, show_progress=False) template_3000_tio = Template.from_npy(template_3000_tio_path) ccf = cross_corr(spectrum.nearest_order(6800), template_3000_tio) ccf.plot() plt.xlabel('Radial Velocity [km/s]') plt.ylabel('CCF') We can see a significant "mean absorption line" in the cross-correlation function of the TiO emission spectrum near the known radial velocity of Proxima Cen at -22 km/s. This is an unsurprisingly significant detection of TiO in the atmosphere of the cool star Proxima Centauri. The `~hipparchus.cross_corr` function returns a `~hipparchus.CCF` object, which stores the resulting cross-correlation function as a function of velocity and its metadata. Let's now take the CCF of the LkCa 4 spectrum and the TiO template: .. code-block:: python from hipparchus import cross_cor ccf = cross_corr(lkca4_spectrum.nearest_order(6800), template_3000_tio) ccf.plot() .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt from hipparchus import Template, EchelleSpectrum, cross_corr lkca4_spectrum_url = 'https://drive.google.com/uc?export=download&id=1x3nIg1P5tYFQqJrwEpQU11XQOs3ImH3v' lkca4_spectrum_path = download_file(lkca4_spectrum_url, cache=True, show_progress=False) spectrum = EchelleSpectrum.from_e2ds(lkca4_spectrum_path) template_3000_tio_url = 'https://drive.google.com/uc?export=download&id=1eGUBfk7Q9zaXgJQJtVFB6pit7cmoGCpn' template_3000_tio_path = download_file(template_3000_tio_url, cache=True, show_progress=False) template_3000_tio = Template.from_npy(template_3000_tio_path) ccf = cross_corr(spectrum.nearest_order(6800), template_3000_tio) ccf.plot() plt.xlabel('Radial Velocity [km/s]') plt.ylabel('CCF') Despite LkCa 4 being classified as a K dwarf, this CCF shows a significant absorption signal due to TiO. This absorption can be understood in the context of the really neat paper by `Gully-Santiago et al 2017 `_, who showed that this star is roughly 80% covered in cool, dark regions where it is cool enough for TiO to form, and 20% covered by hot, bright regions. Hunting for starspots --------------------- Let's now hunt for starspots on Proxima Centauri. Starspots are cool regions on stellar surfaces where strong magnetic fields inhibit convection. Since starspots are cooler than the rest of the stellar photosphere, they may have molecular absorption features that don't occur elsewhere on the star. Proxima Centauri, for example, is a 3000 K star. At 3000 K, we don't expect to see significant absorption due to water molecules, but water begins to show absorption features in the optical at and below 2500 K at wavelengths greater than 5800 Angstroms. So let's cross-correlate each order of the Proxima Centauri echelle spectrum with the high resolution template for water at 2500 K to see if there is significant starspot coverage on Proxima Centauri with temperatures :math:`\Delta T \sim 500` K: .. code-block:: python counter = -1 for order in proxima_spectrum.orders: if order.wavelength.mean() > 5800: counter += 1 ccf = cross_corr(order, template_2500_h2o) ccf.plot(label='{0:.0f} $\AA$'.format(order.wavelength.mean()), color=plt.cm.magma(counter/20)) plt.legend(loc='lower right', fontsize=8) plt.xlabel('$\Delta v$ [km/s]') plt.ylabel('CCF') .. plot:: from astropy.utils.data import download_file import matplotlib.pyplot as plt from hipparchus import Template, EchelleSpectrum, cross_corr proxima_spectrum_url = 'https://drive.google.com/uc?export=download&id=1I7E1x1XRjcxXNQXiuaajb_Jz7Wn2N_Eo' proxima_spectrum_path = download_file(proxima_spectrum_url, cache=True, show_progress=False) spectrum = EchelleSpectrum.from_e2ds(proxima_spectrum_path) template_2500_h2o_url = 'https://drive.google.com/uc?export=download&id=1RIXBl3L3J_R9PQ-k_0BqAtO-9zYn2mag' template_2500_h2o_path = download_file(template_2500_h2o_url, cache=True, show_progress=False) template_2500_h2o = Template.from_npy(template_2500_h2o_path) counter = -1 for order in spectrum.orders: if order.wavelength.mean() > 5800: counter += 1 ccf = cross_corr(order, template_2500_h2o) ccf.plot(label='{0:.0f} $\AA$'.format(order.wavelength.mean()), color=plt.cm.magma(counter/20)) plt.legend(loc='lower right', fontsize=8) plt.xlabel('$\Delta v$ [km/s]') plt.ylabel('CCF') The CCF of each spectral order is shown with a different color curve. You can see that there are no spectral orders which show significant absorption at the radial velocity of Proxima Centauri (-22 km/s). This suggests there is not significant absorption due to water vapor in the atmosphere of Proxima Cen. This could suggest there is insignificant coverage by cool spots, the spots are hotter than 2500 K, or some combination of both scenarios.