In [ ]:
%matplotlib inline
%config IPython.matplotlib.backend = "retina"
from matplotlib import rcParams
rcParams["savefig.dpi"] = 300
rcParams["figure.dpi"] = 300

from celerite import plot_setup
plot_setup.setup(auto=False)

Recovery of a celerite process


In [ ]:
import numpy as np
import matplotlib.pyplot as plt

import celerite
from celerite import terms

np.random.seed(123)

# Simulate some data
kernel = terms.SHOTerm(log_S0=0.0, log_omega0=2.0, log_Q=2.0,
                       bounds=[(-10, 10), (-10, 10), (-10, 10)])
gp = celerite.GP(kernel)
true_params = np.array(gp.get_parameter_vector())
omega = 2*np.pi*np.exp(np.linspace(-np.log(10.0), -np.log(0.1), 5000))
true_psd = gp.kernel.get_psd(omega)
N = 200
t = np.sort(np.random.uniform(0, 10, N))
yerr = 2.5

gp.compute(t, yerr)
y = gp.sample()

fig, ax = plt.subplots(1, 1)
ax.errorbar(t, y, yerr=yerr, fmt=".k", lw=1)
ax.set_ylim(-26, 26)
ax.set_xlim(0, 10)
ax.set_xlabel("time [day]")
ax.set_ylabel("relative flux [ppm]");

In [ ]:
import copy
from scipy.optimize import minimize

def nll(params, gp, y):
    gp.set_parameter_vector(params)
    if not np.isfinite(gp.log_prior()):
        return 1e10
    ll = gp.log_likelihood(y)
    return -ll if np.isfinite(ll) else 1e10

p0 = true_params + 1e-4*np.random.randn(len(true_params))
soln = minimize(nll, p0, method="L-BFGS-B", args=(gp, y))
gp.set_parameter_vector(soln.x)
ml_psd = gp.kernel.get_psd(omega)

ml_gp = copy.deepcopy(gp)
ml_gp.log_likelihood(y)

In [ ]:
import emcee

def log_probability(params):
    gp.set_parameter_vector(params)
    
    lp = gp.log_prior()
    if not np.isfinite(lp):
        return -np.inf

    ll = gp.log_likelihood(y)
    return ll + lp if np.isfinite(ll) else -np.inf

ndim = len(soln.x)
nwalkers = 32
coords = soln.x + 1e-4 * np.random.randn(nwalkers, ndim)

sampler = emcee.EnsembleSampler(nwalkers, ndim, log_probability)
coords, _, _ = sampler.run_mcmc(coords, 500)
sampler.reset()
coords, _, _ = sampler.run_mcmc(coords, 2000)

In [ ]:
# Compute the posterior PSD inference
samples = sampler.flatchain[::15, :]
post_psd = np.empty((len(samples), len(omega)))
for i, s in enumerate(samples):
    gp.set_parameter_vector(s)
    post_psd[i] = gp.kernel.get_psd(omega)
q = np.percentile(post_psd, [16, 50, 84], axis=0)

In [ ]:
x = np.linspace(-0.5, 10.5, 500)
mu, var = ml_gp.predict(y, x, return_var=True)
std = np.sqrt(var)

fig = plt.figure(figsize=plot_setup.get_figsize(1, 2.3))

ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=2)
ax2 = plt.subplot2grid((3, 2), (2, 0), rowspan=1)
ax3 = plt.subplot2grid((3, 2), (0, 1), rowspan=3)
fig.subplots_adjust(hspace=0, wspace=0.4)

ax1.errorbar(t, y, yerr=yerr, fmt=".k", lw=1)
ax1.plot(x, mu)
ax1.fill_between(x, mu+std, mu-std, alpha=0.5, edgecolor="none", zorder=100)
ax1.set_xticklabels([])

ax1.annotate("simulated data", xy=(0, 1), xycoords="axes fraction",
             xytext=(5, -5), textcoords="offset points",
             ha="left", va="top")
ax1.annotate("N = {0}".format(len(t)), xy=(0, 0),
             xycoords="axes fraction",
             xytext=(5, 5), textcoords="offset points",
             ha="left", va="bottom")

pred_mu, pred_var = ml_gp.predict(y, return_var=True)
std = np.sqrt(yerr**2 + pred_var)
ax2.errorbar(t, y - pred_mu, yerr=std, fmt=".k", lw=1)
ax2.axhline(0.0, color="k", lw=0.75)

ax1.set_ylim(-26, 26)
ax1.set_xlim(-0.5, 10.5)
ax2.set_ylim(-9, 9)
ax2.set_xlim(-0.5, 10.5)

ax2.set_xlabel("time [day]")
ax1.set_ylabel("relative flux [ppm]")
ax2.set_ylabel("residuals")

for ax in [ax1, ax2]:
    ax.yaxis.set_label_coords(-0.2, 0.5)

# plot the PSD comparison
factor = 1.0 / (2*np.pi)
f = omega * factor
ax3.plot(f, q[1] * factor)
ax3.fill_between(f, q[0] * factor, q[2] * factor, alpha=0.3)
ax3.plot(f, true_psd * factor, "--k")
ax3.set_xlim(f[0], f[-1])
ax3.set_yscale("log")
ax3.set_xscale("log")
ax3.set_xlabel("frequency [day$^{-1}$]")
ax3.set_ylabel("power [ppm$^2$ day]")

ax2.xaxis.set_label_coords(0.5, -0.3)
ax3.xaxis.set_label_coords(0.5, -0.1)

fig.savefig("correct.pdf", bbox_inches="tight")

In [ ]:
from scipy.linalg import cho_solve, cho_factor

p0 = gp.get_parameter_vector()
fast_timing = %timeit -o log_probability(p0)

def _time_this():
    K = gp.get_matrix(include_diagonal=True)
    factor = cho_factor(K, overwrite_a=True)
    ld = 2.0 * np.sum(np.log(np.diag(factor[0])))
    ll = -0.5*(np.dot(y, cho_solve(factor, y))+ld) + gp.log_prior()

slow_timing = %timeit -o _time_this()

In [ ]:
tau = np.mean(sampler.get_autocorr_time(c=3))
neff = len(sampler.flatchain) / tau

In [ ]:
import json
c = gp.kernel.coefficients
with open("correct.json", "w") as f:
    json.dump(dict(
        N=len(t),
        J=len(c[0]) + len(c[2]),
        tau=tau,
        neff=neff,
        time=fast_timing.average,
        direct_time=slow_timing.average,
        ndim=ndim,
        nwalkers=nwalkers,
        nburn=500,
        nsteps=2000,
    ), f)

In [ ]:
name_map = {
    "kernel:log_S0": r"$\ln(S_0)$",
    "kernel:log_Q": r"$\ln(Q)$",
    "kernel:log_omega0": r"$\ln(\omega_0)$",
}
params = list(zip(
    (name_map[n] for n in gp.get_parameter_names()),
    gp.get_parameter_bounds()
))
with open("correct-params.json", "w") as f:
    json.dump(params, f)

In [ ]: