Time Domain#
In this tutorial we explore how the two core physical components of the SpotEvolutionModel work independently, and then how they combine to produce a realistic stellar lightcurve.
SpotEvolutionModel
The SpotEvolutionModel decomposes starspot-driven photometric variability into two separable components:
Envelope \(\Gamma(t)\): describes how an individual spot grows and decays over time — the birth/death cycle of a spot.
Visibility \(\Pi(t)\): describes how the projected flux contribution of a spot changes as the star rotates — the rotational modulation.
The full statistical GP kernel is a product of both effects. Here we isolate each one to build intuition before combining them.
import sys
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import HTML
sys.path.append("../..")
from spotgp import (
TrapezoidSymmetricEnvelope,
VisibilityFunction,
SpotEvolutionModel,
LightcurveModel,
)
np.random.seed(42)
1. Envelope Model \(\Gamma(t)\): spot growth and decay#
The envelope function \(\Gamma(t)\) describes the normalized spot area as a function of time relative to the spot’s peak size. It captures the growth and decay lifecycle of a single spot: a linear rise to maximum area, a plateau, and a linear decay back to zero.
Here we use the symmetric trapezoid envelope, parameterized by:
lspot\(= \ell\) — plateau duration (days the spot spends at peak size)tau_spot\(= \tau\) — rise/decay timescale (days to grow from zero to peak, and peak back to zero)
Setting visibility=None isolates the envelope: the star does not rotate, and the spot sits fixed at disk center. Only the changing spot size modulates the flux.
envelope = TrapezoidSymmetricEnvelope(
lspot=5.0, # plateau duration [days]
tau_spot=5.0, # rise/decay timescale [days]
)
envelope_only_model = SpotEvolutionModel(
envelope=envelope,
visibility=None,
sigma_k=0.1
)
The analytic expressions for \(\Gamma(t)\), its Fourier transform \(\hat{\Gamma}(\omega)\), and the autocorrelation \(R_\Gamma(\tau)\) are:
env_equations = envelope_only_model.get_sympy()
The simulated lightcurve below shows a single spot placed at disk center (long=0, lat=0) peaking at tmax=10 days. The flux dip follows the trapezoid shape of \(\Gamma(t)\) directly — rising, plateauing, then recovering.
lc_env = LightcurveModel.from_spot_model(
spot_model=envelope_only_model,
nspot=1, # total number of spots to place
tsim=20, # simulation duration [days]
tsamp=0.2, # cadence [days]
lat=0.0,
long=0.0,
tmax=10.0,
)
lc_env.plot_lightcurve()
anim_env = lc_env.animate_lightcurve(fps=20, duration=6)
HTML(anim_env.to_jshtml())
2. Visibility Function \(\Pi(t)\): rotational modulation#
The visibility function encodes how the flux contribution of a spot varies as the star rotates. A spot on the near side of the star contributes more flux deficit than the same spot near the limb or hidden on the far side.
It is parameterized by:
peq\(= P_\mathrm{eq}\) — equatorial rotation period [days]kappa\(= \kappa\) — differential rotation shear (0 = solid-body)inc\(= i\) — stellar inclination [rad] (\(i = \pi/2\) is edge-on)
Setting envelope=None isolates the visibility: the spot has a constant size at all times (alpha_max), and only the rotational modulation changes the flux. The spot is placed at long=0 (disk center at t=tmax) and lat=0 (equator).
visibility = VisibilityFunction(
peq=5.0, # equatorial rotation period [days]
kappa=0., # differential rotation shear
inc=np.pi / 2, # stellar inclination [rad]
)
visibility_only_model = SpotEvolutionModel(
envelope=None,
visibility=visibility,
sigma_k=0.1
)
vis_equations = visibility_only_model.get_sympy()
lc_vis = LightcurveModel.from_spot_model(
spot_model=visibility_only_model,
nspot=1,
tsim=20, # simulation duration [days]
tsamp=0.1, # cadence [days]
alpha_max=0.2, # fixed spot angular radius [rad]
lat=0.0, # equatorial spot
long=0.0, # long=0 places spot at disk center at t=tmax
tmax=10.0,
)
The lightcurve below shows periodic dips as the spot rotates in and out of view. The period of the modulation matches peq=5 days. The spot size is constant — only the geometry changes.
lc_vis.plot_lightcurve()
anim_vis = lc_vis.animate_lightcurve(fps=20, duration=6)
HTML(anim_vis.to_jshtml())