Author: gully

Date: Jan 9, 2015

Fitting Si gaps data with Gaussian Process and emcee.

Originals available at: https://github.com/dfm/gp-tutorial & https://speakerdeck.com/dfm/an-astronomers-introduction-to-gaussian-processes


In [41]:
%pylab inline
# Auto imports np and plt


Populating the interactive namespace from numpy and matplotlib
WARNING: pylab import has clobbered these variables: ['axes', 'f', 'ndim', 'hist', 'bivariate_normal']
`%matplotlib` prevents importing * from pylab and numpy

In [42]:
from matplotlib import rcParams
#rcParams["savefig.dpi"] = 150
import emcee  # http://dan.iel.fm/emcee
import triangle  # https://github.com/dfm/triangle.py
import numpy as np
import matplotlib.pyplot as plt
from astroML.plotting import hist
from astroML.stats.random import bivariate_normal
from matplotlib.patches import Ellipse
import timeit
#from IPython.display import display, Math, Latex

In [43]:
import seaborn as sns
sns.set_context("notebook", font_scale=1.5, rc={"lines.linewidth": 2.5})
cmap = sns.cubehelix_palette(light=1, as_cmap=True)
sns.palplot(sns.cubehelix_palette(light=1))


Non-linear physical optics example

Now with real data.


In [44]:
import pandas as pd
df = pd.read_csv('/Users/gully/Astronomy/Latex/AO_bonding_paper/data/cln_20130218_cary5000.csv')
df.set_index('wavelength', inplace=True)
mask = df.index >= 1250.0
df = df[mask]
df.tail()


Out[44]:
Baseline 100%T Baseline 0%T VG05 VG01 VG06 VG02 VG02_pos2 VG02_pos3 VG02_pos4 VG03_pos1 VG03_pos2 VG04_pos1 VG04_pos2 VG06_post Baseline 100%T.1 Baseline 0%T.1 VG05_post n_Si
wavelength
1290 96.298859 0.002348 52.094208 52.218681 52.098507 35.160301 52.043499 52.175678 32.796074 40.752743 50.996433 36.973141 52.769554 52.606575 97.988457 0.003348 52.060879 3.507617
1280 97.808563 0.001907 51.771324 51.893349 51.776863 35.105576 51.718990 51.851250 32.706768 35.230064 50.615891 37.041645 52.440304 52.282059 98.926910 0.004712 52.049179 3.509110
1270 98.602745 0.002983 51.735073 51.861851 51.742981 34.882565 51.684551 51.818935 32.781158 30.584154 50.542969 37.319405 52.408810 52.248066 99.691551 0.003948 52.035488 3.510641
1260 98.596153 0.003123 52.049667 52.176834 52.055584 35.009571 51.992088 52.131851 32.795376 27.088987 50.845806 37.806896 52.720737 52.564770 100.302528 0.003200 52.024544 3.512211
1250 99.040512 0.002594 52.036583 52.161674 52.037704 35.210747 51.965767 52.108131 32.725964 24.375448 50.827778 38.043575 52.702827 52.544254 100.753906 0.005230 52.007866 3.513821

Read in the data and make an estimate for the covariance.


In [45]:
from etalon import *
np.random.seed(123456)

In [46]:
# Introduce the Real data
x = df.index.values
N = len(x)
# Define T_DSP for the model
T_DSP = T_gap_Si(x, 0.0)
n1 = sellmeier_Si(x)

# Define uncertainty
yerr = 0.0002*np.ones(N)
iid_cov = np.diag(yerr ** 2)

Normalize the measurements by measured DSP Si.


In [47]:
VG03 = df.VG03_pos2.values/df.VG06.values
VG02_pos2 = df.VG02_pos2.values/df.VG06.values
VG02_pos3 = df.VG02_pos3.values/df.VG06.values
VG04_pos2 = df.VG04_pos2.values/df.VG06_post.values

In [48]:
plt.errorbar(x, VG03, yerr=yerr, fmt=".k", capsize=0, label='VG03')
plt.errorbar(x, VG02_pos2, yerr=yerr, fmt=".r", capsize=0, label='VG02_pos2')
plt.errorbar(x, VG02_pos3, yerr=yerr, fmt=".b", capsize=0, label='VG02_pos3')
plt.errorbar(x, VG04_pos2, yerr=yerr, fmt=".g", capsize=0, label='VG04_pos2')
plt.xlabel('$\lambda $(nm)');
plt.ylabel('$T_{gap}$');
plt.title('Measured Si Transmission');
plt.ylim(0.9, 1.01);
plt.legend(loc='best');


Construct guesses for $d$ and $f$ and priors


In [49]:
VG03_guess, VG03_LB, VG03_UB= [4120, 0.045], [3920, 0.0], [4250, 0.5]
VG02p2_guess, VG02p2_LB, VG02p2_UB= [10, 0.01], [0, 0.0], [100, 1.0]
VG02p3_guess, VG02p3_LB, VG02p3_UB= [10, 0.01], [0, 0.0], [100, 1.0]
VG04p2_guess, VG04p2_LB, VG04p2_UB= [10, 0.01], [0, 0.0], [100, 1.0]

Constructing a guess at the covariance matrix

The off-diagonal terms are characterized by parameters $a$ and $s$.

The term $a$ controls the strength of the interaction. The term $s$ controls the range of correlation.

First we define a natural log likelihood function. You hand it a $d$, a $f$, an $a$, and an $s$ and it gives you the likelihood of the data: $\ln{p}=\ln{p(y|d,f,a,s)}$

Note: x is not passed to this function. The way Python works, x, y, and iid_cov are searched for in the local namespace, then global namespace.


In [59]:
def lnlike(d, f, lna, lns):
    a, s = np.exp(lna), np.exp(lns)
    off_diag_terms = a**2 * np.exp(-0.5 * (x[:, None] - x[None, :])**2 / s**2)
    C = iid_cov + off_diag_terms
    sgn, logdet = np.linalg.slogdet(C)
    if sgn <= 0:
        return -np.inf
    r = VG02_pos2 - T_gap_Si_withFF_fast(x, d, f, n1)/T_DSP
    return -0.5 * (np.dot(r, np.linalg.solve(C, r)) + logdet)

In [95]:
# Apply a uniform prior over some range.
# Shape params are uniform in log space 
def lnprior(d, f, lna, lns):
    if not (0.0 < d < 150.0 and 
            0.0 < f < 1.0 and
            -12 < lna < -2 and
            0 < lns < 10):
        return -np.inf
    return 0.0

In [96]:
def lnprob(p):
    lp = lnprior(*p)
    if not np.isfinite(lp):
        return -np.inf
    return lp + lnlike(*p)

Run emcee!


In [97]:
sampler.reset()

In [106]:
ndim, nwalkers = 4, 32
a_Guess = 0.0016
s_Guess = 100.0
p0 = np.array([10.0, 0.5, np.log(a_Guess), np.log(s_Guess)]) # Decent guess
pos = [p0 + 1.0e-2*p0 * np.random.randn(ndim) for i in range(nwalkers)]

sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob)

In [107]:
# This is the burn-in
pos, lp, state = sampler.run_mcmc(pos, 200)

In [118]:
sampler.reset()
pos, lp, state = sampler.run_mcmc(pos, 3000)
chain = sampler.chain

In [119]:
fig, axes = plt.subplots(4, 1, figsize=(5, 6), sharex=True)
fig.subplots_adjust(left=0.1, bottom=0.1, right=0.96, top=0.98,
                    wspace=0.0, hspace=0.05)
[a.plot(np.arange(chain.shape[1]), chain[:, :, i].T, "k", alpha=0.5)
 for i, a in enumerate(axes)]
[a.set_ylabel("${0}$".format(l)) for a, l in zip(axes, ["d", "f", "\ln a", "\ln s"])]
axes[-1].set_xlim(0, chain.shape[1])
axes[-1].set_xlabel("iteration");



In [120]:
samples = sampler.flatchain
samples_lin = copy(samples)
samples_lin[:, 2:] = np.exp(samples_lin[:, 2:])

In [121]:
fig = triangle.corner(samples_lin, 
                      labels=map("${0}$".format, ["d", "f", "a", "s"]), 
                      quantiles=[0.16, 0.84])


Quantiles:
[(0.16, 14.221705604216806), (0.84, 134.627866046718)]
Quantiles:
[(0.16, 0.012199803903377876), (0.84, 0.63788907020506613)]
Quantiles:
[(0.16, 0.0016300118926985193), (0.84, 0.0020847958988104648)]
Quantiles:
[(0.16, 23.569198844267106), (0.84, 26.650961709859601)]

In [122]:
off_diag_terms = 0.0018**2 * np.exp(-0.5 * (x[:, None] - x[None, :])**2 / 12.0**2)
C = iid_cov + off_diag_terms
fit = T_gap_Si_withFF_fast(x, 4093.5, 0.04572, n1)/T_DSP

vec = np.random.multivariate_normal(fit, C)

In [125]:
for d, f, a, s in samples_lin[np.random.randint(len(samples), size=500)]:
    #plt.plot(x, T_gap_Si_withFF_fast(x, d, f, n1)/T_DSP, color="r", alpha=0.03)
    off_diag_terms = a**2 * np.exp(-0.5 * (x[:, None] - x[None, :])**2 / s**2)
    C = iid_cov + off_diag_terms
    fit = T_gap_Si_withFF_fast(x, d, f, n1)/T_DSP
    vec = np.random.multivariate_normal(fit, C)
    plt.plot(x, vec, color="g", alpha=0.01)
    
plt.errorbar(x, VG02_pos2, fmt=".k")
plt.xlabel('$\lambda$');
plt.ylabel('$T_{gap}$');
plt.title('Observed Si Transmission');
plt.ylim(0.9, 1.01);



In [115]:
d_mcmc, f_mcmc, a_mcmc, s_mcmc = map(lambda v: (v[1], v[2]-v[1], v[1]-v[0]),
                             zip(*np.percentile(samples_lin, [16, 50, 84],
                                                axis=0)))

In [116]:
d_mcmc, f_mcmc, a_mcmc, s_mcmc


Out[116]:
((18.437764390680083, 14.144913860888654, 5.6906855110380263),
 (0.38372509151310463, 0.38449751998749848, 0.25855915588612211),
 (0.0019081122442764246, 0.00023248306410016172, 0.00019825863436056385),
 (25.37003035799334, 1.6257489134961887, 1.3994701329105652))

In [ ]: