Doppler maps#

spotter can be used to model time series spectra of non-uniform stars. In this tutorial we showcase some of these features.

Important

The features shown in this tutotrial are still experimental and not tested against ground truth. Use with care!

A simple spotted surface#

We first create a surface with a single circular spot

from spotter import Star
from spotter import core, show
import matplotlib.pyplot as plt

base_star = Star.from_sides(2**4, period=0.02, u=(0.2, 0.2), inc=1.4)
spot = core.soft_spot(base_star.sides, 0.0, 0.0, 0.15)

plt.figure(figsize=(3, 3))
show(base_star - spot, 0.63)
../../_images/811d1971fdb19569492b1e40f7b33320fe8f876d2fde1f758fb9bac562c2a513.png

Here we will assume a stellar spectrum made of a single line with a Gaussian profile

import matplotlib.pyplot as plt
import jax.numpy as jnp


def gaussian(x, mu, sigma):
    return jnp.exp(-((x - mu) ** 2) / (2 * sigma**2))


wv = jnp.linspace(400, 410, 500) * 1e-9
star_spectrum = 1 - 0.5 * gaussian(wv, wv.mean(), 0.5e-9)

and we will assume that the star and its spot share the same spectrum.

In spotter, we are in charge of setting the spectrum of each individual pixel. We do that by instantiating the Star object with a spectral spatial map of shape (wavelength, pixels)

import warnings

warnings.filterwarnings("error")

spectra = star_spectrum[:, None] * (base_star - spot).y

star = base_star.set(y=spectra, wv=wv)
star.y.shape
(500, 3072)

This way, star.y holds the len(wv) spatial maps of the star. Here is a plot to realize what that means

from matplotlib import gridspec

idxs = jnp.array([100, 210, 250, 290, 450])

fig = plt.figure(figsize=(7, 3))
axes = gridspec.GridSpec(2, len(idxs))
ax = plt.subplot(axes[1, :])
ax.plot(wv * 1e9, star_spectrum)
ax.plot(wv[idxs] * 1e9, star_spectrum[idxs], ".", c="C0")
ax.set_xlabel("wavelength (nm)")
ax.set_ylabel("normalized flux")
ax.set_title("Stellar spectrum", fontsize=10)

for i, idx in enumerate(idxs):
    ax = plt.subplot(axes[0, i])
    show(star[idx], vmax=1)
    ax.set_title(f"{wv[idx] * 1e9:.1f} nm", fontsize=10)

_ = plt.tight_layout()
../../_images/17c3192255c2bfebc21e1b5e9f4cd4f68d1da82def769f4095d21531500fafe2.png

In the last plot we used the fact that star[i] returns the star at the i-th wavelength.

From there we can compute the observed intensity of the star at a given time

from spotter.doppler import spectrum

observed_spectrum = spectrum(star, 0.0)

Let’s plot the observed spectrum at different phases

from spotter.doppler import spectrum

n = 5
fig, axes = plt.subplots(2, n, figsize=(8, 2.5))
nonspotted_spectrum = spectrum(star, star.period / 2)

for i, phase in enumerate(jnp.linspace(jnp.pi / 2, -jnp.pi / 2, n)):
    ax = axes[1, i]
    time = phase * star.period / (2 * jnp.pi)
    ax.plot(spectrum(star, time), c="k", lw=1, label="spotted")
    ax.plot(nonspotted_spectrum, "-", alpha=0.3, c="k", lw=1, label="non-spotted")
    ax.axis("off")

    ax = axes[0, i]
    show(star - spot, phase, ax=ax)

    if i == n - 1:
        plt.legend()
../../_images/3a42f0bf7da1e6df8e0e01cfe7fa0a0e24408411885264e4cf5d87525e954d67.png

Note

In this example we used the same polynomial limb darkening coefficients across all wavelengths, which is not very realistic. The Star.u argument also accept a set of (wavelength) limb darkening coefficients to be used at each wavelength.

Spectral spatial maps#

Because of their cooler temperatures (among other factors), starspots usually have their own spectra, different from the star’s. Hence, in this section, we define the spectra of the star and the spot separately

wv = jnp.linspace(400, 425, 500) * 1e-9
star_spectrum = 1 - gaussian(wv, wv.mean() - 0.2 * jnp.ptp(wv), 0.5e-9)
spot_spectrum = 0.6 * (1 - gaussian(wv, wv.mean() + 0.2 * jnp.ptp(wv), 0.5e-9))

plt.plot(wv * 1e9, star_spectrum, label="star spectrum")
plt.plot(wv * 1e9, spot_spectrum, label="spot spectrum")
plt.legend()
plt.xlabel("wavelength (nm)")
_ = plt.ylabel("normalized flux")
../../_images/a48c813a2a2f8ee9bdb308af05c78c84360e7880924fd34519083abf80264dc0.png

The next step consists in setting the spectra of each pixel in the map

import numpy as np

# setting the spectrum of the unspotted part of the star
spectra = (base_star.y - spot) * star_spectrum[:, None]

# setting the spectrum of the spotted part of the star
spectra = spectra + spot[None, :] * spot_spectrum[:, None]
star = base_star.set(y=spectra, wv=wv)

As in the previous example, here is the observed spectrum at different phases

n = 5
fig, axes = plt.subplots(2, n, figsize=(8, 2.5))

for i, phase in enumerate(jnp.linspace(jnp.pi / 1.8, -jnp.pi / 1.8, n)):
    ax = axes[1, i]
    time = phase * star.period / (2 * jnp.pi)
    ax.plot(spectrum(star, time), c="k", lw=1, label="chromatic spot")
    ax.plot(
        spectrum(star, time),
        c="k",
        lw=1,
        alpha=0.3,
        label="non-chromatic spot",
    )
    ax.axis("off")

    ax = axes[0, i]
    show(base_star - spot, phase, ax=ax)

    if i == n - 1:
        plt.legend()
../../_images/daf6de8cd4efcda3657083bfc39895424b90bf572ea7ff23c3d44b2e8b4a5962.png