Analysis of KELT-11b

This notebook demonstrates the main features of pycheops for the analysis of as single transit of KELT-11 b observed by CHEOPS.

To run this notebook ...

  • Install an up-to-date version python3 (version 3.7 or later), e.g., https://www.anaconda.com/distribution/
  • Install the latest version of pycheops using "pip install pycheops" - you may also need the "--upgrade" and/or "--user" options
  • Configure your pycheops installation

    >>> from pycheops.core import setup_config
  • Install the DACE API (https://dace.unige.ch/pythonAPI/)

Setup

Using pylab to import numpy, scipy and matplotlib.

Use %pylab notebook for interactive plots, but be aware that plots may not appear in the window you expect if you do not stop the plot interaction in one cell and then run plotting commands in another cell.

In [1]:
%pylab inline
import pycheops
Populating the interactive namespace from numpy and matplotlib

Properties of KELT-11b

The light curve for a planet of radius $R_p$ transiting a star of radius $R_s$ with impact parameter $b$ is calculated using the power-2 limb-darkening law, $$I_{\lambda}(\mu) = 1 - c(1 - \mu^{\alpha}),$$ and is described by the following parameters.

  • T_0: time of mid-transit
  • P: orbital period
  • D: depth, $(R_p/R_s)^2 = k^2$
  • W: width, $(R_s/a)\sqrt{(1+k)^2 - b^2}/\pi$
  • b: impact parameter, $a\cos(i)/R_s$
  • c: flux scaling factor
  • f_c: $\sqrt{e}\cos(\omega)$
  • f_s: $\sqrt{e}\sin(\omega)$
  • h1: $I{\lambda}(0.5) = 1 - c(1-2^{-\alpha})$
  • h2: $I{\lambda}(0.5) - I_{\lambda}(0) = c\cdot2^{-\alpha}$

See help(pycheops.models.TransitModel) for more information on the transit model.

For a circular orbit, the parameter W is the transit duration in phase units (not true for eccentric orbits).

Initial values for some/all of these parameters can be obtained using the PlanetProperties class. For CHEOPS science team members, you can use the latest information on the planet stored in a table at DACE using the keyword option "query_dace=True. For non-CST members, or if the planet is not in the DACE table, the planet information is obtained from TEPCat.

In [2]:
DACE_ACCESS = True

if DACE_ACCESS:
    Kelt11b = pycheops.PlanetProperties('KELT-11b',query_dace=True)
else:
    Kelt11b = pycheops.PlanetProperties('KELT-11b',query_dace=False,
                                        ecosw=ufloat(-0.0040, 0.0490), 
                                        esinw=ufloat(0.0310, 0.0680))
print(Kelt11b)
Target  KELT-11b  found in DACE-Planets.
Identifier : KELT-11b
T0 : 2458918.5017 +/- 0.0007 BJD       [DACE-Planets]
P :     4.7365250 +/- 0.0000670 days   [DACE-Planets]
D :  2100.0000 +/- 100.0000 ppm         [DACE-Planets]
W :  0.2908 +/- 0.0014 days            [DACE-Planets]
ecosw : -0.0040 +/- 0.0490             [DACE-Planets]
esinw : +0.0310 +/- 0.0680             [DACE-Planets]
ecc : 0.0313 +/- 0.0677                [Derived]
omega : +1.69912 +/- 1.57949 radian    [Derived]
f_c : -0.0226 +/- 0.2760               [Derived]
f_s : +0.1753 +/- 0.1963               [Derived]

Load the dataset and select an aperture size for the light curve data

A query to DACE is used to obtain the file key for the observation of KELT-11b during in-orbit commissioning (prog_id=24).

If the data are not already in your pycheops cache directory then pycheops will attempt to downloaded them from DACE (assuming you have setup your access correctly).

There may be multiple versions of the data set in DACE so we use the data_proc_num attribute to select the latest version.

The data reduction pipeline (DRP) report is shown when you download the report. To view the report again at a later time you can use the command dataset.view_report()

The output from dataset.get_lightcurve is the numpy arrays time, flux and fluxerr. We do not need these yet so we send the output to a variable ``.

N.B. you must decide whether or not to apply a correction for contamination of the aperture by nearby stars (decontaminate=True or decontaminate=False). There is some uncertainty in this correction so, in some cases, using decontaminate=True can add noise to the light curve.

In [3]:
from dace.cheops import Cheops
myfilter = {'obj_id_catname': {'contains':'KELT-11b','prog_id':24}}
data = Cheops.query_database(filters=myfilter)
i_last_data_proc_num = np.argmax(data['data_proc_num'])
file_key = data['file_key'][i_last_data_proc_num]
dataset = pycheops.Dataset(file_key)
_ = dataset.get_lightcurve(aperture='OPTIMAL',decontaminate=True)
Found archive tgzfile /Users/pflm/pycheops/data/CH_PR300024_TG000101_V0101.tgz
 PI name     : Andrea FORTIER
 OBS ID      : 1015981
 Target      : KELT-11b
 Coordinates : 10:46:49.74 -09:23:56.5
 Spec. type  : G9V
 V magnitude : 7.83 +- 0.00
Light curve data loaded from  /Users/pflm/pycheops/data/CH_PR300024_TG000101_V0101-OPTIMAL.fits
Time stored relative to BJD = 2458918
Aperture radius used = 30 arcsec
UTC start:  2020-03-09T14:50:41
UTC end:    2020-03-10T04:44:34
Visit duration: 50033 s
Exposure time: 2 x 15.0 s
Number of non-flagged data points: 1535
Efficiency (non-flagged data): 92.0 %
Light curve corrected for flux from background stars
Mean counts = 39657493.5
Median counts = 39673630.7
RMS counts = 73740.9 [1859 ppm]
Median standard error = 6348.9 [160 ppm]
Mean contamination = 41.3 ppm
Mean smearing correction = 21.4 ppm
Predicted amplitude of ramp = 81 ppm

View animation of the data cube

This is an important step in order to understand the instrumental effects that may be present in your data. It may take a few minutes to generate the movie.

In [4]:
dataset.animate_frames(vmax=0.5)
Subarray data loaded from  /Users/pflm/pycheops/data/CH_PR300024_TG000101_V0101-SubArray.fits

Subarray is saved in the current directory as KELT-11b-subarray.gif
Out[4]:
<matplotlib.animation.ArtistAnimation at 0x7ff5285791d0>
 dataset.lc

The items in the dictionary dataset.lc are

  • time = BJD-dataset.bjd_ref
  • flux = observed flux scaled by its median value
  • flux_err - standard error on flux
  • bjd_ref - BJD at the start of the day of observations
  • table - astropy table created from binary table stored in the FITS file
  • xoff, yoff - offset from nominal target location to measured flux centroid
  • contam - fraction of measured flux due to contamination of aperture by other stars
  • centroid_x, centroid_y - measured flux centroid position (pixels)
  • smear - smear trail correction calculated by the DRP
  • deltaT - telescope tube temperature CE_thermFront_2 + $12^{\circ}$C
  • roll_angle - computed mean roll angle of the CCD
  • aperture - aperture radius in pixels
In [5]:
dataset.lc.keys()
Out[5]:
dict_keys(['time', 'flux', 'flux_err', 'bjd_ref', 'table', 'header', 'xoff', 'yoff', 'bg', 'contam', 'smear', 'deltaT', 'centroid_x', 'centroid_y', 'roll_angle', 'aperture'])
Outlier rejection

An initial cut for obvious outliers is applied here. Data points with discrepant fluxes are rejected from all the arrays in dataset.lc except lc['table']. The output of the function is the clipped arrays time, flux, and flux_err.

In [6]:
time,flux,flux_err = dataset.clip_outliers(verbose=True);
Rejected 10 points more than 5.0 x MAD = 942 ppm from the median

Diagnostic plot

Useful as a quick-look of the data and meta-data to look for any obvious correlations or problems.

In [7]:
dataset.diagnostic_plot()
Make a plot of the data "by-hand"

The plot shows a well-defined transit with a depth of about 0.2% and some little "spikes" that seem to occur regularly - we will deal with these below.

In [8]:
plot(time,flux,'r+') # matplotlib
bjd_ref = dataset.bjd_ref
xlabel(f'BJD - {bjd_ref}') # example of python f-string formatting
ylabel('Flux')
title(dataset.target)
Out[8]:
Text(0.5, 1.0, 'KELT-11b')
Plot of flux against roll-angle

Many of the instrumental artefacts in the CHEOPS light curves are associated with the spacecraft roll angle. In this case we can see the the "spikes" occur near a roll angle of 340 degrees.

Note the use of a temporary variable t that "points" to the object we are using in the plot command.

In [9]:
t = dataset.lc
plot(t['roll_angle'],t['flux'],'gx')   # plot with green crosses, just for a change
xlabel('Roll angle [degrees]')
ylabel('Flux')
xlim(0,360)
Out[9]:
(0.0, 360.0)
Ramp correction

A flux ramp is often observed in the beginning of a visit with an amplitude of a few hundred ppm (either positive or negative) and decaying over a time scale of several hours.

This ramp is due to a small scale change in the shape of the PSF. This in turn can be understood as a slight focus change as a result of a thermal adaptation of the telescope tube to the new heat load by the thermal radiation from the Earth. This thermal adaptation (breathing) is monitored by thermal sensors in the tube.

At the time of writing (Dec 2020) several algorithms are being investigated to correct for this ramp effect. One algorithm that is simple to implement and seems to work quite well is to correct the measured flux using the equation $$ {\rm Flux}_{\rm corrected} = {\rm Flux}_{\rm measured} (1+\beta \Delta T) $$ where $$ \Delta T = T_{\rm thermFront\_2} + 12 $$

The following values of the coefficient $\beta$ have been determined by Goran Olofsson.

Aperture $\beta$
22.5 0.00014
25.0 0.00020
30.0 0.00033
40.0 0.00040

The method correct_ramp uses linear interpolation in this table to predict the slope $\beta$ for the aperture radius of the light curve.

In [10]:
time,flux,flux_err = dataset.correct_ramp(plot=True)
Plot of contamination against roll-angle

Notice in the previous cell that the output from the last command is printed to screen - the semi-colon at the end of the last command here avoids this little issue.

The contamination in this case is a small fraction of the total flux.

In [11]:
plot(t['roll_angle'],t['contam'],'bo',ms=3)  # blue dots, marker size =3
xlabel('Roll angle [degrees]')
ylabel('contam')
xlim(0,360);

Just as an illustration, mask data with x-centroid values < 813.5

This is only done as an example of how to mask the data - there is no good reason to exclude these data points from the analysis.

As with clip_outliers, all the internal arrays except lc['table'] are masked.

In [12]:
mask = dataset.lc['centroid_x'] < 813.5
time,flux,flux_err = dataset.mask_data(mask)
Masked 5 points

Stellar data

Properties of the star such as Teff and log g are taken from SWEET-Cat. The SWEET-Cat catalogue is downloaded automatically if it is not in your pycheops cache folder, or if the cached version is more than a day old.

The values of Teff, logg and [Fe/H] are used to estimate the limb-darkening parameters of the star and the mean stellar density $\rho_{\star}$.

See help(pycheops.StarProperties) for instructions on how to specifiy your own values.

CHEOPS Science team members can use the option dace=True to use updated values from the StarProperties table stored on DACE.

In [13]:
star = pycheops.StarProperties(dataset.target)  # dace=True
print(star)
Identifier : KELT-11b
Coordinates: 10:46:49.74 -09:23:56.5
T_eff :  5370 +/-  50 K   [SWEET-Cat]
log g :  3.73 +/- 0.04    [SWEET-Cat]
[M/H] : +0.18 +/- 0.07    [SWEET-Cat]
log rho : -1.17 +/- 0.08  (solar units)
h_1 : 0.715 +/- 0.011     [Stagger]
h_2 : 0.442 +/- 0.050     [Stagger]

Least squares fit

The dataset method lmfit_transit uses the Levenberg-Marquardt method implemented in lmfit to find a least-squares best fit for a transit model calculated using the qpower2 algorithm.

Parameter values can be specified in various ways

  • Fixed value, e.g., P=1.234
  • Free parameter with uniform prior interval specified as a 2-tuple, e.g., dfdx=(-1,1). The initial value is taken as the the mid-point of the allowed interval.
  • Free parameter with uniform prior interval and initial value specified as a 3-tuple, e.g., (0.1, 0.2, 1)
  • Free parameter with a Gaussian prior specified as a ufloat, e.g., ufloat(0,1).
  • as an lmfit Parameter, e.g., "lmfit.Parameter(value=0.01,min=0.001,max=0.1,vary=True)"

To enable decorrelation against a parameter, specifiy it as a free parameter, e.g., dfdbg=(0,1). The decorrelation parameters that are available can be found using help(pycheops.models.FactorModel)

Decorrelation is done against is a scaled version of the quantity specified with a range of either (-1,1) or, for strictly positive quantities, (0,1). This means the coefficients dfdx, dfdy, etc. correspond to the amplitude of the flux variation due to the correlation with the relevant parameter.

Where possible, the initial values for lmfit are taken from the KELT11b PlanetProperties object. These values are stored as ufloat objects. The .n attribute of a ufloat is its value and the .s attribute is the standard error.

In [14]:
from uncertainties import ufloat
from uncertainties.umath import sqrt as usqrt

P = Kelt11b.P.n
BJD_0 = Kelt11b.T0.n
cycle = round((dataset.bjd_ref-BJD_0)/P)
T_0 = BJD_0 - dataset.bjd_ref + cycle*P

D = Kelt11b.D.n/1e6  # Depth stored in ppm
W = Kelt11b.W.n/P    # Width stored in days

if Kelt11b.f_c and Kelt11b.f_s:
    f_c = Kelt11b.f_c
    f_s = Kelt11b.f_s
else:
    # From Pepper et al., 2017
    ecosw = ufloat(-0.004,0.05099)
    esinw = ufloat(0.031,0.055)
    ecc = usqrt(ecosw**2+esinw**2)
    f_s = esinw/usqrt(ecc)  # f_s = sqrt(e)sin(omega) = e.sin(omega)/sqrt(e)
    f_c = ecosw/usqrt(ecc)  # f_c = sqrt(e)cos(omega) = e.cos(omega)/sqrt(e)

Re-normalization

Use a straight-line fit to the data either side of transit to set the out-of-eclipse level to 1.

In [15]:
plot(time,flux,'.',color='gray')
time,flux,flux_err = dataset.flatten(T_0,P*W)
plot(time,flux,'.',color='blue')
axvline(T_0-P*W/2,color='darkcyan',ls=':')
axvline(T_0+P*W/2,color='darkcyan',ls=':')
axhline(1,color='darkcyan',ls=':')
xlabel('Phase')
ylabel('Flux');

Initial fit - no decorrelation

We will use this residuals from this fit to calculate priors on the parameters of the decorrelation model.

There is only one transit in the dataset so the orbital period is fixed for this analysis.

The output from lmfit_transit() is an lmfit MinimizerResult object.

In [16]:
lmfit0 = dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho)

Plots of least-squares fit

  • flux values are light blue points
  • binned flux values are dark blue error bars
  • transit model is the green line
  • transit model + trends is the brown line

Note the ";" at the end of the plot command to stop the plot appearing twice. Alternatively, you can put the output of the plot command (which is a matplotlib Figure object) into a variable. This can be used later to save the plot to a file, for example.

In [17]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14);

Prior for decorrelation parameters

Decorrelation is done against is a scaled version of the quantity specified with a range of either (-1,1) or, for strictly positive quantities, (0,1). This means the coefficients dfdx, dfdy, etc. correspond to the amplitude of the flux variation due to the correlation with the relevant parameter.

A sensible prior for these coefficients (decorrelation parameters) is a normal distribution with the same standard deviation as the residuals from the fit above with no decorrelation.

Careful selection of the prior is important because it will be used later to determine if we have selected a good set of decorrelation parameters.

In [18]:
sigma_0 = lmfit0.rms
dprior = ufloat(0, sigma_0)
print(f'sigma_0 = {sigma_0:0.6f}')
# Prior on dfdt is different because it is measured in 1/day, so 
tprior = ufloat(0, sigma_0/np.ptp(time))
sigma_0 = 0.000234
Model selection

The question of which decorrelation parameters to include is an example of model selection. In this case we have nested models in which one or more of the parameters is fixed in the simpler model.

For models $M_0$ and $M_1$ with parameters $\theta_0 = \{p_1, p_2, \dots, p_n, 0\}$ and $\theta_1 = \{p_1, p_2, \dots, p_n, \psi\}$, given the data $D$, the Bayes factor $B_{01}$ is defined by

$$ \frac{P(M_0|D)}{P(M_1|D)} = \frac{P(M_0)}{P(M_1)} \frac{P(D|M_0)}{D|P(M_1)} = \frac{P(M_0)}{P(M_1)} B_{01},$$

where $ P(D|M_0) = \int P(D|\theta_0)P(\theta_0)d^n\theta$ and similarly for $P(D|M_1)$. $P(\theta_0)$ is the prior on the parameters of model $M_0$.

The prior on the extra parameter are the same for both models so we can use the Savage-Dickey Density Ratio to calculate the Bayes factor $$ B_{01} = \frac{P(\psi=0|D)}{P(\psi=0)} $$

For a normal prior with standard deviation $\sigma_0$, $P(\psi=0) = 1/\sigma_0\sqrt{2\pi}$.

The posterior probability distributions for the decorrelation parameters are normally well-behaved and close to Gaussian, as expected for a linear model. Assuming that they are normally distributed and that the standard deviation is given accurately by the error on the parameter given by lmfit, and that a priori the two models are equally likely, we can calculate Bayes factor for models with/without parameter with value $p\pm\sigma_p$ from lmfit using

$$B_p = e^{-(p/\sigma_p)^2/2}\sigma_0/\sigma_p$$

These are the values listed in the section [[Bayes Factors]] in the report above. Parameters with Bayes factors $\overset{>}{\sim} 1$ are not supported by the data and can be removed from the model. In this example we can safely remove dftdt. We could instead remove dfdsin2phi, but we will keep it because it is one of a set.

N.B. this statistic is only valid for comparison of the models with/without one parameter, so parameters should be removed one-by-one and the test repeated for every new pair of models.

From the initial fit with no decorrelation we use the RMS of the residuals to set the priors on the decorrelation parameters, $\mathcal{N}(0, \sigma_p)$ or, for ${df}/{dt}$, $\mathcal{N}(0, \sigma_p/\Delta t)$ where $\Delta t$ is the duration of the visit. We then added decorrelation parameters to the fit one-by-one, selecting the parameter with the lowest Bayer factor at each step and stopping when $B_p>1$ for all remaining parameters. This process sometimes leads to a set of parameters that includes a few parameters that are strongly correlated with one another and so are therefore not well determined, i.e. they have large Bayes factors. So, after adding parameters one-by-one, go through a process of repeatedly removing the parameter with the largest Bayes factor if any of the parameters have a Bayes factors $B_p>1$.

For a derivation of the Savage-Dickey Density Ratio see Trotta 2007MNRAS.378...72T

dfdt is a linear trend with time and dfdcontam is a linear trend v. the contamination of the aperture by nearby stars. The contamination is estimated using the Gaia catalogue. If decontaminate=True is used in get_lightcurve() then this value is subtracted from the measured flux, so this value should be close to zero.

In [19]:
detrend = {}
bestbf = 0
allpar = ['dfdsinphi','dfdcosphi',
          'dfdsin2phi','dfdcos2phi',
          'dfdsin3phi','dfdcos3phi',
          'dfdx', 'dfdy', 'dfdsmear',
          'dfdbg','dfdt', 'dfdcontam']

print('Parameter     BF     Delta_BIC RMS(ppm)')
while bestbf < 1:
    bestbf = np.inf
    for p in allpar:
        dtmp = detrend.copy()
        dtmp[p] = tprior if p is 'dfdt' else dprior
        lmfit = dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **dtmp)
        bre = re.compile(r'{}: *(\d+\.\d{{3}})\n'.format(p))
        m = bre.findall(dataset.lmfit_report())
        if len(m) > 0:
            bf = float(m[-1])
            if bf < bestbf:
                bestbf = bf
                newpar = p

    if bestbf < 1: 
        print(f'+{newpar:<12s} {bestbf:6.2f}  {lmfit.bic-lmfit0.bic:8.1f} {1e6*lmfit.rms:8.1f}')
        detrend[newpar] = tprior if newpar is 'dfdt' else dprior
        allpar.remove(newpar)

worstbf = 10
while worstbf > 1:
    worstbf = 0
    for p in detrend:
        bre = re.compile(r'{}: *(\d+\.\d{{3}})\n'.format(p))
        m = bre.findall(dataset.lmfit_report())
        if len(m) > 0:
            bf = float(m[-1])
            if bf > worstbf:
                worstbf = bf
                delpar = p
    if worstbf > 1:
        del detrend[delpar] 
        lmfit = dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend)
        print(f'-{delpar:<12s} {worstbf:6.2f}  {lmfit.bic-lmfit0.bic:8.1f} {1e6*lmfit.rms:8.1f}')
    
Parameter     BF     Delta_BIC RMS(ppm)
+dfdcosphi      0.00      -2.7    233.5
+dfdcos2phi     0.00     -83.2    230.1
+dfdsin3phi     0.00    -106.2    228.9
+dfdcos3phi     0.00    -161.2    226.5
+dfdbg          0.00    -200.7    224.7
+dfdy           0.39    -491.5    212.6
+dfdcontam      0.44    -510.5    211.5
-dfdcosphi     12.59    -507.1    212.0
-dfdsin3phi     1.11    -530.7    211.3

Re-run lmfit without selected decorrelation parameters

In [20]:
lmfit = dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend)
print(dataset.lmfit_report(min_correl=0.5))
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 427
    # data points      = 1493
    # variables        = 13
    chi-square         = 0.24595082
    reduced chi-square = 1.6618e-04
    Akaike info crit   = -20734.3162
    Bayesian info crit = -20665.3052
    RMS residual       = 211.3 ppm
[[Variables]]
    T_0:         0.50122531 +/- 0.00132214 (0.26%) (init = 0.5017)
    P:           4.736525 (fixed)
    D:           0.00226922 +/- 3.5623e-05 (1.57%) (init = 0.0021)
    W:           0.06211433 +/- 0.00116926 (1.88%) (init = 0.06139522)
    b:           0.56184993 +/- 0.04472497 (7.96%) (init = 0.5)
    f_c:        -0.01304348 +/- 0.05949966 (456.16%) (init = -0.02262488)
    f_s:         0.13210403 +/- 0.12505896 (94.67%) (init = 0.1753428)
    h_1:         0.70102073 +/- 0.00792392 (1.13%) (init = 0.715)
    h_2:         0.442 (fixed)
    c:           0.99997362 +/- 1.2752e-05 (0.00%) (init = 1)
    dfdbg:       6.7254e-04 +/- 4.2301e-05 (6.29%) (init = 0)
    dfdcontam:  -1.4977e-04 +/- 3.4221e-05 (22.85%) (init = 0)
    dfdy:        1.0318e-04 +/- 3.6980e-05 (35.84%) (init = 0)
    dfdcos2phi:  4.2237e-05 +/- 7.8666e-06 (18.62%) (init = 0)
    dfdcos3phi: -3.9595e-05 +/- 8.1520e-06 (20.59%) (init = 0)
    k:           0.04763630 +/- 3.7390e-04 (0.78%) == 'sqrt(D)'
    aR:          4.53131861 +/- 0.21270974 (4.69%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:        0.99228314 +/- 0.00123335 (0.12%) == 'sqrt(1 - (b/aR)**2)'
    logrho:     -1.25455917 +/- 0.06116012 (4.88%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:           0.01762161 +/- 0.03435152 (194.94%) == 'f_c**2 + f_s**2'
    q_1:         0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:         0.46419485 +/- 0.01420058 (3.06%) == '(h_1-h_2)/(1-h_2)'
[[Correlations]] (unreported correlations are < 0.500)
    C(W, f_s)       =  0.993
    C(D, b)         =  0.869
    C(f_c, f_s)     = -0.837
    C(W, f_c)       = -0.821
    C(c, dfdcontam) = -0.718
    C(W, b)         =  0.675
    C(b, f_s)       =  0.666
[[Priors]]
    T_0:        0.50170000 +/- 1.0000e-03
    f_c:       -0.02262488 +/- 0.27596689
    f_s:        0.17534285 +/- 0.19625065
    h_1:        0.71500000 +/- 0.01100000
    dfdbg:      0.00000000 +/- 2.3386e-04
    dfdcontam:  0.00000000 +/- 2.3386e-04
    dfdy:       0.00000000 +/- 2.3386e-04
    dfdcos2phi: 0.00000000 +/- 2.3386e-04
    dfdcos3phi: 0.00000000 +/- 2.3386e-04
    logrho:    -1.16766000 +/- 0.07958858
[[Bayes Factors]]  (values >~1 => free parameter probably not useful)
    dfdbg:             0.000
    dfdcontam:         0.000
    dfdy:              0.129
    dfdcos2phi:        0.000
    dfdcos3phi:        0.000
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

In this plot (detrend=True)

  • (flux/trend) values are light blue points
  • binned (flux/trend) values are dark blue error bars
  • transit model is the green line
  • transit model/trend is the brown line (same as green line in this case)
In [21]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=True);

Roll angle plot

Same color-coding of data and models as before.

In [22]:
fig = dataset.rollangle_plot()

Try modelling excess flux near roll angle 340$^{\circ}$ as internal reflection ("glint")

The method add_glint() is being used here to calculate a spline fit to the residuals from the last fit. A scaled version of this function (which is stored in dataset) can then be added to the model.

In [23]:
glint_func = dataset.add_glint(nspline=48,binwidth=5,figsize=(10,4),gapmax=5)

Fit including glint

The parameter glint_scale includes a linear scaling of the glint function we created as part of the model. Here we allow the glint to be scaled between 0 (no glint) and 2 (twice as much glint as estimated from the previous fit).

In [24]:
dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend,
                      glint_scale=(0,2))
print(dataset.lmfit_report(min_correl=0.5))
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 261
    # data points      = 1493
    # variables        = 14
    chi-square         = 0.25103859
    reduced chi-square = 1.6974e-04
    Akaike info crit   = -20897.0723
    Bayesian info crit = -20822.7527
    RMS residual       = 204.4 ppm
[[Variables]]
    T_0:          0.50140304 +/- 0.00128015 (0.26%) (init = 0.5017)
    P:            4.736525 (fixed)
    D:            0.00212887 +/- 4.5911e-05 (2.16%) (init = 0.0021)
    W:            0.06190046 +/- 0.00219879 (3.55%) (init = 0.06139522)
    b:            0.35884722 +/- 0.08904791 (24.81%) (init = 0.5)
    f_c:         -0.08549837 +/- 2.1562e-04 (0.25%) (init = -0.02262488)
    f_s:          0.10029888 +/- 0.20434356 (203.73%) (init = 0.1753428)
    h_1:          0.69358145 +/- 0.00722774 (1.04%) (init = 0.715)
    h_2:          0.442 (fixed)
    c:            0.99997961 +/- 1.2354e-05 (0.00%) (init = 1)
    dfdbg:        6.2599e-04 +/- 4.1213e-05 (6.58%) (init = 0)
    dfdcontam:   -1.4193e-04 +/- 3.3230e-05 (23.41%) (init = 0)
    dfdy:         8.7703e-05 +/- 3.5810e-05 (40.83%) (init = 0)
    dfdcos2phi:   3.9599e-05 +/- 7.6432e-06 (19.30%) (init = 0)
    dfdcos3phi:  -3.6923e-05 +/- 7.9635e-06 (21.57%) (init = 0)
    glint_scale:  1.00266581 +/- 0.10565266 (10.54%) (init = 1)
    k:            0.04613964 +/- 4.9752e-04 (1.08%) == 'sqrt(D)'
    aR:           5.05316143 +/- 0.22616527 (4.48%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:         0.99747529 +/- 0.00125460 (0.13%) == 'sqrt(1 - (b/aR)**2)'
    logrho:      -1.11254345 +/- 0.05831339 (5.24%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:            0.01736984 +/- 0.04098958 (235.98%) == 'f_c**2 + f_s**2'
    q_1:          0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:          0.45086281 +/- 0.01295293 (2.87%) == '(h_1-h_2)/(1-h_2)'
[[Correlations]] (unreported correlations are < 0.500)
    C(W, f_s)       =  0.998
    C(T_0, f_c)     = -0.986
    C(D, b)         =  0.956
    C(c, dfdcontam) = -0.718
[[Priors]]
    T_0:         0.50170000 +/- 1.0000e-03
    f_c:        -0.02262488 +/- 0.27596689
    f_s:         0.17534285 +/- 0.19625065
    h_1:         0.71500000 +/- 0.01100000
    dfdbg:       0.00000000 +/- 2.3386e-04
    dfdcontam:   0.00000000 +/- 2.3386e-04
    dfdy:        0.00000000 +/- 2.3386e-04
    dfdcos2phi:  0.00000000 +/- 2.3386e-04
    dfdcos3phi:  0.00000000 +/- 2.3386e-04
    logrho:     -1.16766000 +/- 0.07958858
[[Bayes Factors]]  (values >~1 => free parameter probably not useful)
    dfdbg:              0.000
    dfdcontam:          0.001
    dfdy:               0.325
    dfdcos2phi:         0.000
    dfdcos3phi:         0.001
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

Plot the results

In [25]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=True,
                   title='With glint v. roll-angle');
In [26]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=False,
                   title='With glint  v. roll-angle');
In [27]:
fig = dataset.rollangle_plot()
Glint correction for internal reflection from the Moon.

The previous fit is better, but the extra flux does not appear at exactly the same roll angle every time. The reason from this can be found by checking for solar system objects near the field of view. We see that the Moon was only 16$^{\circ}$ from the target. The position of the Moon varies noticeably during the observation, so we must account for this when we model the glint.

In [28]:
dataset.planet_check()
BJD = 2458918.125228298
Body     R.A.         Declination  Sep(deg)
-------------------------------------------
Moon     11:21:45.98  +09:19:22.6      16.0
Mars     19:05:24.63  -23:08:37.9      81.6
Jupiter  19:29:58.35  -21:50:58.1     113.6
Saturn   20:03:17.22  -20:27:01.5     125.8
Uranus   02:06:47.07  +12:20:30.2     129.0
Neptune  23:18:02.80  -05:36:58.0     163.1

Try modelling excess flux near roll angle 340 as Moon glint

Residuals from last fit will already be corrected for glint, so we can either re-run lmfit without glint_scale and re-fit, or just fit the out-of transit flux.

For this example, let's create a mask so that we can fit the out-of-transit flux.

In [29]:
from pycheops.utils import phaser
phase = phaser(time,lmfit.params['P'],lmfit.params['T_0'],-0.5)
mask = abs(phase) < lmfit.params['W']/2
plot(phase[mask],flux[mask],'.',color='gray')
plot(phase[~mask],flux[~mask],'.',color='blue')
xlim(-0.09,0.09)
xlabel('Phase')
ylabel('Flux');
Creating glint function v. moon angle
  • using fit_flux to fit flux values, not residuals
  • using mask=mask to use mask created above to exclude in-transit data
  • using moon=True to account for motion of the Moon

The previous glint function is overwritten by this new glint function.

Note the the spike v. Moon angle is sharper than against roll angle.

In [30]:
moon_glint = dataset.add_glint(moon=True,nspline=48,binwidth=5,
                               fit_flux=True, mask=mask,
                              figsize=(10,5))
Least-squares fit including glint v. moon angle
In [31]:
dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend,
                      glint_scale=(0,2))
 
print(dataset.lmfit_report(min_correl=0.5))
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 204
    # data points      = 1493
    # variables        = 14
    chi-square         = -0.51638846
    reduced chi-square = -3.4915e-04
    Akaike info crit   = -20926.0151
    Bayesian info crit = -20851.6955
    RMS residual       = 203.1 ppm
[[Variables]]
    T_0:          0.50141998 +/- 0.00127041 (0.25%) (init = 0.5017)
    P:            4.736525 (fixed)
    D:            0.00223703 +/- 2.7853e-05 (1.25%) (init = 0.0021)
    W:            0.06164864 +/- 2.9070e-04 (0.47%) (init = 0.06139522)
    b:            0.50762943 +/- 0.03082640 (6.07%) (init = 0.5)
    f_c:          0.06138020 +/- 0.14560972 (237.23%) (init = -0.02262488)
    f_s:          0.05795863 +/- 1.2369e-04 (0.21%) (init = 0.1753428)
    h_1:          0.69812674 +/- 0.00745326 (1.07%) (init = 0.715)
    h_2:          0.442 (fixed)
    c:            1.00001334 +/- 1.2729e-05 (0.00%) (init = 1)
    dfdbg:        2.0409e-04 +/- 5.9006e-05 (28.91%) (init = 0)
    dfdcontam:   -1.4857e-04 +/- 3.2897e-05 (22.14%) (init = 0)
    dfdy:         8.7161e-05 +/- 3.5570e-05 (40.81%) (init = 0)
    dfdcos2phi:   2.1162e-05 +/- 7.7980e-06 (36.85%) (init = 0)
    dfdcos3phi:  -1.6676e-05 +/- 8.0937e-06 (48.54%) (init = 0)
    glint_scale:  0.74678309 +/- 0.06805201 (9.11%) (init = 1)
    k:            0.04729730 +/- 2.9445e-04 (0.62%) == 'sqrt(D)'
    aR:           4.72982302 +/- 0.09730705 (2.06%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:         0.99422396 +/- 7.0355e-04 (0.07%) == 'sqrt(1 - (b/aR)**2)'
    logrho:      -1.19869830 +/- 0.02680433 (2.24%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:            0.00712673 +/- 0.01787520 (250.82%) == 'f_c**2 + f_s**2'
    q_1:          0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:          0.45900849 +/- 0.01335710 (2.91%) == '(h_1-h_2)/(1-h_2)'
[[Correlations]] (unreported correlations are < 0.500)
    C(T_0, f_s)           =  0.985
    C(D, b)               =  0.915
    C(W, f_c)             =  0.860
    C(dfdbg, glint_scale) = -0.726
    C(c, dfdcontam)       = -0.690
[[Priors]]
    T_0:         0.50170000 +/- 1.0000e-03
    f_c:        -0.02262488 +/- 0.27596689
    f_s:         0.17534285 +/- 0.19625065
    h_1:         0.71500000 +/- 0.01100000
    dfdbg:       0.00000000 +/- 2.3386e-04
    dfdcontam:   0.00000000 +/- 2.3386e-04
    dfdy:        0.00000000 +/- 2.3386e-04
    dfdcos2phi:  0.00000000 +/- 2.3386e-04
    dfdcos3phi:  0.00000000 +/- 2.3386e-04
    logrho:     -1.16766000 +/- 0.07958858
[[Bayes Factors]]  (values >~1 => free parameter probably not useful)
    dfdbg:              0.010
    dfdcontam:          0.000
    dfdy:               0.327
    dfdcos2phi:         0.755
    dfdcos3phi:         3.460
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

Simplify model

The glint function will remove some of the count-rate variation with roll angle, so repeat the process of removing decorrelation parameters one-by-one.

In [32]:
worstbf = 10
while worstbf > 1:
    worstbf = 0
    for p in detrend:
        bre = re.compile(r'{}: *(\d+\.\d{{3}})\n'.format(p))
        m = bre.findall(dataset.lmfit_report())
        if len(m) > 0:
            bf = float(m[-1])
            if bf > worstbf:
                worstbf = bf
                delpar = p
    if worstbf > 1:
        del detrend[delpar] 
        lmfit = dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend)
        print(f'-{delpar:<12s} {worstbf:6.2f}  {lmfit.bic-lmfit0.bic:8.1f} {1e6*lmfit.rms:8.1f}')
    
dataset.lmfit_transit(T_0 = ufloat(T_0, 0.001), P=P, 
                      D=(D/4, D, D*4), W=(W/4, W, W*4),b=(0,0.5,1),
                      f_c=f_c, f_s=f_s, 
                      h_1=star.h_1, h_2=star.h_2.n,
                      logrhoprior=star.logrho, **detrend,
                      glint_scale=(0,2))
 
print(dataset.lmfit_report(min_correl=0.5))
-dfdcos3phi     3.46    -507.9    212.5
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 259
    # data points      = 1493
    # variables        = 13
    chi-square         = 0.20179549
    reduced chi-square = 1.3635e-04
    Akaike info crit   = -20842.5493
    Bayesian info crit = -20773.5383
    RMS residual       = 206.7 ppm
[[Variables]]
    T_0:          0.50141529 +/- 0.00129555 (0.26%) (init = 0.5017)
    P:            4.736525 (fixed)
    D:            0.00248890 +/- 3.1231e-05 (1.25%) (init = 0.0021)
    W:            0.06226866 +/- 1.5457e-04 (0.25%) (init = 0.06139522)
    b:            0.72855886 +/- 0.01844569 (2.53%) (init = 0.5)
    f_c:          0.05936513 +/- 0.00109223 (1.84%) (init = -0.02262488)
    f_s:          0.14694501 +/- 0.03946866 (26.86%) (init = 0.1753428)
    h_1:          0.71671478 +/- 0.00870517 (1.21%) (init = 0.715)
    h_2:          0.442 (fixed)
    c:            1.00001811 +/- 1.3107e-05 (0.00%) (init = 1)
    dfdbg:        1.5176e-04 +/- 5.9449e-05 (39.17%) (init = 0)
    dfdcontam:   -1.4447e-04 +/- 3.2935e-05 (22.80%) (init = 0)
    dfdy:         9.3245e-05 +/- 3.6354e-05 (38.99%) (init = 0)
    dfdcos2phi:   2.1232e-05 +/- 7.8378e-06 (36.91%) (init = 0)
    glint_scale:  0.81648158 +/- 0.06718954 (8.23%) (init = 1)
    k:            0.04988891 +/- 3.1301e-04 (0.63%) == 'sqrt(D)'
    aR:           3.86435321 +/- 0.09189967 (2.38%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:         0.98206682 +/- 9.1636e-04 (0.09%) == 'sqrt(1 - (b/aR)**2)'
    logrho:      -1.46200253 +/- 0.03098437 (2.12%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:            0.02511705 +/- 0.01161291 (46.24%) == 'f_c**2 + f_s**2'
    q_1:          0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:          0.49232039 +/- 0.01560067 (3.17%) == '(h_1-h_2)/(1-h_2)'
[[Correlations]] (unreported correlations are < 0.500)
    C(D, f_c)             = -0.982
    C(D, b)               =  0.805
    C(b, f_c)             = -0.773
    C(dfdbg, glint_scale) = -0.717
    C(c, dfdcontam)       = -0.696
    C(W, h_1)             = -0.547
[[Priors]]
    T_0:         0.50170000 +/- 1.0000e-03
    f_c:        -0.02262488 +/- 0.27596689
    f_s:         0.17534285 +/- 0.19625065
    h_1:         0.71500000 +/- 0.01100000
    dfdbg:       0.00000000 +/- 2.3386e-04
    dfdcontam:   0.00000000 +/- 2.3386e-04
    dfdy:        0.00000000 +/- 2.3386e-04
    dfdcos2phi:  0.00000000 +/- 2.3386e-04
    logrho:     -1.16766000 +/- 0.07958858
[[Bayes Factors]]  (values >~1 => free parameter probably not useful)
    dfdbg:              0.151
    dfdcontam:          0.000
    dfdy:               0.240
    dfdcos2phi:         0.761
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1
Plot the results.

Note that for the roll angle plot there are now 3 panels. The residuals in the top panel are corrected for glint but not for other trends - the model for these is shown in brown. The fit of the glint function to the residuals corrected for other trends is shown the middle panel. The lower panel shows the residuals from the complete model, i.e., flux - (transit $\times$ trends + glint)

In [33]:
fig = dataset.rollangle_plot()
In [34]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=False,
                  title='Including moon glint ');
In [35]:
dataset.plot_lmfit(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=True,
                  title='Including moon glint ');

GP fit with fixed transit parameters

The "bumps-and-wiggles" in the residuals from the previous fit are very likely to be due to real flux variations from the star due to convection in its atmosphere a.k.a. granulation noise or "flicker" (Sulis et al. 2020). The time scale ($\sim$ hour) and amplitude ($\sim 100$ppm) of the variation are typical granulation noise observed in other cool sub-giant stars.

A Gaussian Process (GP) is a way to model the granulation noise by specifying the properties of the correlation matrix for this correlated noise. We will use the celerite package to do this because it can do the calculation of the likelihood for a given noise function very quickly, and it includes a kernel that is appropriate for granulation noise.

Our noise model will then have three components

  • Gaussian white noise with standard deviation per-point given by flux_err.
  • Extra Gaussian white noise ("jitter") with standard deviation per-point $\sigma$
  • Correlated noise described by a GP calculated using the SHOTerm kernel in celerite with fixed shape parameter $Q=1/\sqrt{2}$.

With $Q=1/\sqrt{2}$, the kernel that describes the covariance between observations separated by a time $\tau$ is $$ k(\tau) = S_0\omega_0 e^{-\omega_0\tau/\sqrt{2}}\cos\left(\omega_0\tau/\sqrt{2} - \pi/4\right)$$

The free parameters of the noise model are then $\sigma$ (amplitude of the jitter), $\omega_0$ (related to correlation time scale) and $S_0$ (related to amplitude of the correlated noise). These are all strictly positive values for which we want to use a scale-free prior so we use the log of these values when we sample the posterior probability distribution (PPD). The sampling of the PPD is done using emcee.

Directly fitting a GP to the data with all the transit and decorrelation parameters as free parameters leads to large errors on the transit parameters and a GP that implies much more variability in the flux than is seen in CHEOPS data for similar stars, i.e., the GP tries to model the transit as noise. To avoid this, we first do a GP fit with the transit parameters fixed. The results from this fit are used to put priors on the parameters of the noise model in the final fit.

In [36]:
# Copy the parameters from the last lmfit
params_fixed = dataset.lmfit.params.copy() 

# Fix the transit parameters
for p in ['T_0','D','W','b']:
    params_fixed[p].set(vary=False)

# Use emcee to sample the PPD. Note we add the SHOTerm at this step.
result = dataset.emcee_sampler(params=params_fixed, add_shoterm=True, 
                               burn=512,steps=256,nwalkers=128)

print(dataset.emcee_report(min_correl=0.5))
Running burn-in ..
100%|██████████| 512/512 [09:03<00:00,  1.06s/it]
Running sampler ..
100%|██████████| 256/256 [04:35<00:00,  1.07s/it]
[[Fit Statistics]]
    # fitting method   = emcee
    # function evals   = 92659
    # data points      = 1493
    # variables        = 12
    chi-square         = 2492.92249
    reduced chi-square = 1.68326974
    Akaike info crit   = -21408.5051
    Bayesian info crit = -21344.8026
    RMS residual       = 207.0 ppm
[[Variables]]
    T_0:          0.5014153 (fixed)
    P:            4.736525 (fixed)
    D:            0.002488904 (fixed)
    W:            0.06226866 (fixed)
    b:            0.7285589 (fixed)
    f_c:          0.03558342 +/- 0.18727928 (526.31%) (init = -0.02262488)
    f_s:          0.12973201 +/- 0.08313893 (64.09%) (init = 0.1753428)
    h_1:          0.71421453 +/- 0.00984078 (1.38%) (init = 0.715)
    h_2:          0.442 (fixed)
    c:            1.00000843 +/- 3.1052e-05 (0.00%) (init = 1)
    dfdbg:        2.4831e-04 +/- 5.7021e-05 (22.96%) (init = 0)
    dfdcontam:   -1.2728e-04 +/- 7.1006e-05 (55.79%) (init = 0)
    dfdy:         8.0434e-05 +/- 3.0890e-05 (38.40%) (init = 0)
    dfdcos2phi:   2.1141e-05 +/- 2.1944e-05 (103.80%) (init = 0)
    glint_scale:  0.74197131 +/- 0.07274981 (9.80%) (init = 1)
    k:            0.04988891 +/- 3.1301e-04 (0.63%) == 'sqrt(D)'
    aR:           3.86435321 +/- 0.09189967 (2.38%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:         0.98206682 +/- 9.1636e-04 (0.09%) == 'sqrt(1 - (b/aR)**2)'
    logrho:      -1.46200253 +/- 0.03098437 (2.12%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:            0.01809658 +/- 0.01161291 (64.17%) == 'f_c**2 + f_s**2'
    q_1:          0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:          0.48783965 +/- 0.01560067 (3.20%) == '(h_1-h_2)/(1-h_2)'
    log_S0:      -23.3738274 +/- 0.27175351 (1.16%) (init = -12)
    log_omega0:   5.61297403 +/- 0.12654211 (2.25%) (init = 3)
    log_Q:       -0.3465736 (fixed)
    log_sigma:   -9.67993512 +/- 0.18140239 (1.87%) (init = -10)
    sigma_w:      62.5255603 == 'exp(log_sigma)*1e6'
[[Correlations]] (unreported correlations are < 0.500)
    C(log_S0, log_omega0) = -0.765
    C(c, dfdcontam)       = -0.715
    C(dfdbg, glint_scale) = -0.664
[[Priors]]
    T_0:         0.50170000 +/- 1.0000e-03
    f_c:        -0.02262488 +/- 0.27596689
    f_s:         0.17534285 +/- 0.19625065
    h_1:         0.71500000 +/- 0.01100000
    dfdbg:       0.00000000 +/- 2.3386e-04
    dfdcontam:   0.00000000 +/- 2.3386e-04
    dfdy:        0.00000000 +/- 2.3386e-04
    dfdcos2phi:  0.00000000 +/- 2.3386e-04
    logrho:     -1.16766000 +/- 0.07958858
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

With detrend=False (the default) the upper panel shows the unbinned and binned flux values used in the fit (light blue and dark blue, as before), the transit model (green) and the best-fit (maximum likelihood) model including all trends, glint and the GP noise model. Several samples from the PPD are used to plot other models that give a reasonable fit to the data in light brown.

The lower panel shows the residuals from non-GP parts of the model, i.e., flux - (transit $\times$ trends + glint). The GP noise model is shown in brown.

In [37]:
fig  = dataset.plot_emcee(binwidth=0.008, figsize=(10,6), fontsize=14,
                          title='Fixed transit parameters')

With detrend=True, the flux values in the upper panel have been corrected for instrumental trends and glint. The transit model is shown in green, and the transit model + GP noise models are shown in brown (best-fit and samples, as before). The residuals are plotted in the lower panel in the same way as for detrend=False.

In [38]:
fig = dataset.plot_emcee(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=True,
                          title='Fixed transit parameters')

Complete emcee sampler run with priors

We now create Gaussian priors on the noise model parameters centered on the mean of the PPD from the previous fit, and the standard deviation is set to twice the width the PPD. This keeps the noise model in a reasonable region of its parameter space while allowing us to explore if the transit model parameters depend strongly on the noise model. This is done by setting the user_data attribute of the lmfit Parameter object to a ufloat value.

We already added the SHOTerm to the model in the previous step so we do not need to include "add_shoterm" in the call to emcee_sampler again. The output from this emcee run will overwrite the previous results stored in the dataset object. We are also over-writing the "result" variable that stores the output from the previous emcee run as an lmfit MinimizerResult object - we could give the result a different variable name here if we want to save those results.

In [39]:
# Get a copy of the parameters from the previous emcee run
params_prior = dataset.emcee.params.copy()

# Setting the noise model parameters
from uncertainties import ufloat
for p in ['log_S0', 'log_omega0', 'log_sigma']:
    params_prior[p].user_data=ufloat(params_prior[p].value,2*params_prior[p].stderr)

# Restoring the transit model parameters as free parameters
for p in ['T_0','D','W','b']:
    params_prior[p].set(vary=True)
    
# Run emcee again
result = dataset.emcee_sampler(params=params_prior, burn=512, steps=256, nwalkers=256)
print(dataset.emcee_report(min_correl=0.5))
Running burn-in ..
100%|██████████| 512/512 [19:26<00:00,  2.28s/it]
Running sampler ..
100%|██████████| 256/256 [10:00<00:00,  2.35s/it]
[[Fit Statistics]]
    # fitting method   = emcee
    # function evals   = 236885
    # data points      = 1493
    # variables        = 16
    chi-square         = 2412.94524
    reduced chi-square = 1.63367992
    Akaike info crit   = -21405.9734
    Bayesian info crit = -21321.0367
    RMS residual       = 203.7 ppm
[[Variables]]
    T_0:          0.50160912 +/- 5.8731e-04 (0.12%) (init = 0.5017)
    P:            4.736525 (fixed)
    D:            0.00221631 +/- 9.1122e-05 (4.11%) (init = 0.0021)
    W:            0.06188798 +/- 0.00114480 (1.85%) (init = 0.06139522)
    b:            0.49541811 +/- 0.10233735 (20.66%) (init = 0.5)
    f_c:          0.00925839 +/- 0.21715689 (2345.52%) (init = -0.02262488)
    f_s:          0.04290927 +/- 0.12491216 (291.11%) (init = 0.1753428)
    h_1:          0.71106696 +/- 0.01049164 (1.48%) (init = 0.715)
    h_2:          0.442 (fixed)
    c:            0.99999683 +/- 3.3704e-05 (0.00%) (init = 1)
    dfdbg:        2.5609e-04 +/- 5.8848e-05 (22.98%) (init = 0)
    dfdcontam:   -1.1767e-04 +/- 7.1175e-05 (60.49%) (init = 0)
    dfdy:         8.0495e-05 +/- 3.2093e-05 (39.87%) (init = 0)
    dfdcos2phi:   2.3976e-05 +/- 2.1266e-05 (88.70%) (init = 0)
    glint_scale:  0.73698866 +/- 0.07096669 (9.63%) (init = 1)
    k:            0.04707772 +/- 3.1301e-04 (0.66%) == 'sqrt(D)'
    aR:           4.74451027 +/- 0.09189967 (1.94%) == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:         0.99453337 +/- 9.1636e-04 (0.09%) == 'sqrt(1 - (b/aR)**2)'
    logrho:      -1.19465880 +/- 0.03098437 (2.59%) == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:            0.00192692 +/- 0.01161291 (602.67%) == 'f_c**2 + f_s**2'
    q_1:          0.31136400 +/- 0.00000000 (0.00%) == '(1-h_2)**2'
    q_2:          0.48219885 +/- 0.01560067 (3.24%) == '(h_1-h_2)/(1-h_2)'
    log_S0:      -23.3500328 +/- 0.23960955 (1.03%) (init = -12)
    log_omega0:   5.59978907 +/- 0.11857850 (2.12%) (init = 3)
    log_Q:       -0.3465736 (fixed)
    log_sigma:   -9.65917682 +/- 0.13610760 (1.41%) (init = -10)
    sigma_w:      63.8370497 == 'exp(log_sigma)*1e6'
[[Correlations]] (unreported correlations are < 0.500)
    C(W, f_s)             =  0.880
    C(D, b)               =  0.873
    C(log_S0, log_omega0) = -0.710
    C(dfdbg, glint_scale) = -0.678
    C(c, dfdcontam)       = -0.640
[[Priors]]
    T_0:         0.50170000 +/- 1.0000e-03
    f_c:        -0.02262488 +/- 0.27596689
    f_s:         0.17534285 +/- 0.19625065
    h_1:         0.71500000 +/- 0.01100000
    dfdbg:       0.00000000 +/- 2.3386e-04
    dfdcontam:   0.00000000 +/- 2.3386e-04
    dfdy:        0.00000000 +/- 2.3386e-04
    dfdcos2phi:  0.00000000 +/- 2.3386e-04
    logrho:     -1.16766000 +/- 0.07958858
    log_S0:     -23.3738274 +/- 0.54350701
    log_omega0:  5.61297403 +/- 0.25308422
    log_sigma:  -9.67993512 +/- 0.36280477
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

In [40]:
fig  = dataset.plot_emcee(binwidth=0.008, figsize=(10,6), fontsize=14, detrend=False,
                          title='GP prior')

Here we save a the plot of the final fit to the light curve as a file

In [41]:
fig  = dataset.plot_emcee(binwidth=0.008, figsize=(10,6), fontsize=16, detrend=True)
fig.savefig('kelt11fig.png')

Corner plots

We can use a corner plot to view correlations between model parameters. Gaussian priors on parameters are shown using dashed-green lines to indicate the $\pm$ 1-sigma limits. In this first corner plot we can see how the PPD of the noise model parameters are narrower than their priors, i.e., the noise models are still being determined mostly by the data and the priors are only preventing these parameters from going to very large or small values.

In [42]:
font = {'family': 'serif', 'color':  'black', 'weight': 'normal','size': 18}
fig = dataset.corner_plot(
    plotkeys=['log_S0','log_omega0','log_sigma'],
    kwargs={'label_kwargs':{'fontdict':font}})
In [43]:
fig = dataset.corner_plot(plotkeys='all',kwargs={'label_kwargs':{'fontdict':font}})
In [44]:
fig = dataset.rollangle_plot()

Trail plot

A trail plot is shows the parameter values v. step number for all the walkers in the sampler. This is a very effective way to check that the sampler has converged, i.e., that the sampler is randomly sampling the posterior probability distribution.

In a "well-mixed" chain, the trail plot will look like noise for all the walkers and there will be no trends in the position or width of the trails.

In [45]:
fig = dataset.trail_plot('all')

FFT of the residuals

Lomb-Scargle power-spectrum of the residuals.

The previous fit included a GP which is not included in the calculation of the residuals, i.e., the power spectrum includes the power "fitted-out" using the GP. The assumption here is that the GP has been used to model stellar variability that we wish to characterize using the power spectrum.

The red vertical dotted lines show the CHEOPS orbital frequency and its first two harmonics.

The likely range of $\nu_{\rm max}$ (peak power for stellar oscillation) is shown using green dashed lines.

In [46]:
fig = dataset.plot_fft(star, gsmooth=10)

Mass-radius plot

Once we are happy with the fit to the transit light curve we can use the parameters derived to calculate the mass and radius of the planet. To do this we need to specify the semi-amplitude of the star's orbit due to the planet (K in m/s) and provide the star's mass and/or radius. If only the mass is provided then the radius is calculated from the stellar density obtained from the transit width, and vice versa.

For more details on the options in dataset.massradius, see the inline help for pycheops.funcs.massradius.

In [47]:
r,fig=dataset.massradius(m_star=(1.44, 0.06), K=(18.5,1.7),jovian=False)
fig.savefig('KELT-11b_massradius.png')
[[Mass/radius]]
    m_star   =      1.440 +/-      0.060 (    -0.060,    +0.060) M_Sun
    r_star   =       2.84 +/-       0.17 (     -0.16,     +0.18) R_Sun
    e        =      0.044 +/-      0.054 (    -0.032,    +0.076)
    r_p      =       14.6 +/-        1.1 (      -1.0,      +1.2) R_Earth
    m_p      =       62.0 +/-        6.0 (      -6.0,      +6.0) M_Earth
    q        =   0.000129 +/-   0.000012 ( -0.000012, +0.000012)
    a        =      13.40 +/-       0.19 (     -0.19,     +0.18) R_Sun
    a        =    0.06233 +/-    0.00086 (  -0.00088,  +0.00085) au
    rho_star =      0.063 +/-      0.011 (    -0.010,    +0.012) rho_Sun
    g_p      =       2.85 +/-       0.49 (     -0.47,     +0.52) m.s-2
    log g_p  =      2.454 +/-      0.075 (    -0.078,    +0.072) [cgs]
    rho_p    =     0.0199 +/-     0.0048 (   -0.0044,   +0.0052) rho_Earth
    rho_p    =      0.109 +/-      0.026 (    -0.024,    +0.029) [g.cm-3]

Save the dataset

This will save all the data and results from the last least-squares fit and the last emcee fit.

In [48]:
dataset.save();

MultiVisit

MultiVisit can be used to analyse one of more datasets previously saved from pycheops.

The fit automagically includes the following model to account for trends in the data correlated with roll angle: $$\sum_{j=1}^n \alpha_j\sin(j\cdot\Omega t) + \beta_j\cos(j\cdot\Omega t),$$ where, $\Omega$ is the angular rotation frequency of the spacecraft. The free parameters $\alpha_j$ and $\beta_j$ are never calculated explicitly. Instead, they are implicitly accounted for in the calculation of the likelihood using the trick explained by Rodrigo et al. (2017RNAAS...1....7L). The number of terms in this model ($n=3$ by default) can be set using the parameter nroll. To disable this feature using unroll=False.

If a saved dataset includes any other decorrelation parameters as free parameters in the last fit, e.g., dfdt, dfdbg etc., then these are also included as free parameters in the fit to that dataset with Multivisit. The same applies to glint_scale.

The unroll method in MultiVisit assumes that the spacecraft roll angle changes linearly with time at a constant rate $\Omega$. The value of $\Omega$ is determined separately for each dataset. In fact there are smooth variations in $\Omega$ so the results from MultiVisit will not be quite the same as those obtained using dfdsinphi .. dfdcos3phi in Dataset.

See help(MultiVisit) for more details on the following functions within MultiVisit

  • tzero
  • fit_transit
  • fit_eclipse
  • fit_eblm
  • fit_report
  • plot_fit
  • trail_plot
  • corner_plot
  • massradius

N.B. time values in MultiVisit objects are stored as BJD-2457000 (same as TESS) so we use the tzero() method here to calculate the time of mid-transit closest to the mid-point of the observations.

In [49]:
from pycheops import MultiVisit
M = MultiVisit('KELT-11b', id_kws={'dace':False})
TJD_0 = M.tzero(BJD_0,P)
result = M.fit_transit(log_omega0=params_prior['log_omega0'], 
                       log_S0=params_prior['log_S0'],
                       f_c=f_c, f_s=f_s, 
                       unroll=True, nroll=1,
                       extra_priors={
                           'T_0':ufloat(TJD_0,0.01),
                           'h_1':star.h_1,
                           'h_2':star.h_2,
                           'logrho':star.logrho},
                       burn=512, steps=256, nwalkers=256) 
print(M.fit_report(min_correl=0.8))
Identifier : KELT-11b
Coordinates: 10:46:49.74 -09:23:56.5
T_eff :  5370 +/-  50 K   [SWEET-Cat]
log g :  3.73 +/- 0.04    [SWEET-Cat]
[M/H] : +0.18 +/- 0.07    [SWEET-Cat]
log rho : -1.17 +/- 0.08  (solar units)
h_1 : 0.715 +/- 0.011     [Stagger]
h_2 : 0.442 +/- 0.050     [Stagger]


 N  file_key                   Aperture last_ GP  Glint pipe_ver
 ---------------------------------------------------------------------------
  1 CH_PR300024_TG000101_V0101 OPTIMAL  emcee Yes Yes   cn01t-20200615T224519
Running burn-in ..
100%|██████████| 512/512 [07:14<00:00,  1.18it/s]
Running sampler ..
100%|██████████| 256/256 [03:22<00:00,  1.27it/s]
[[Fit Statistics]]
    # fitting method   = emcee
    # function evals   = 256492
    # data points      = 1493
    # variables        = 15
    chi-square         = 1643.69333
    reduced chi-square = 1.11210645
    Akaike info crit   = -21380.1758
    Bayesian info crit = -21300.5477
    RMS residual       = 168.1 ppm
[[Variables]]
    D:               0.00213044 +/- 7.4812e-05 (3.51%) (init = 0.002216312)
    W:               0.06217423 +/- 0.00147698 (2.38%) (init = 0.06188798)
    b:               0.33906087 +/- 0.15061680 (44.42%) (init = 0.4954181)
    P:               4.736525 (fixed)
    T_0:             1918.50198 +/- 6.3364e-04 (0.00%) (init = 1918.502)
    f_c:             0.01792521 +/- 0.21614359 (1205.81%) (init = -0.02262488)
    f_s:             0.06527753 +/- 0.12461199 (190.90%) (init = 0.1753428)
    h_1:             0.70931254 +/- 0.00770617 (1.09%) (init = 0.711067)
    h_2:             0.442 (fixed)
    k:               0.04615667 == 'sqrt(D)'
    aR:              5.06684724 == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:            0.99775851 == 'sqrt(1 - (b/aR)**2)'
    logrho:         -1.10901954 == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:               0.00458247 == 'f_c**2 + f_s**2'
    q_1:             0.31136400 == '(1-h_2)**2'
    q_2:             0.47905473 == '(h_1-h_2)/(1-h_2)'
    log_sigma_w:    -9.67057392 +/- 0.16382363 (1.69%) (init = -6)
    log_S0:         -23.2607668 +/- 0.27208128 (1.17%) (init = -12)
    log_omega0:      5.54793995 +/- 0.12776676 (2.30%) (init = 3)
    log_Q:          -0.3465736 (fixed)
    c_01:            0.99998291 +/- 3.4220e-05 (0.00%) (init = 0.9999968)
    dfdbg_01:        1.9859e-04 +/- 6.9622e-05 (35.06%) (init = 0.0002560888)
    dfdcontam_01:   -4.2377e-05 +/- 8.3506e-05 (197.05%) (init = -0.0001176727)
    dfdy_01:         5.9769e-05 +/- 3.2041e-05 (53.61%) (init = 8.049547e-05)
    glint_scale_01:  0.78386059 +/- 0.07816104 (9.97%) (init = 0.7369887)
[[Correlations]] (unreported correlations are < 0.800)
    C(W, f_s) =  0.891
    C(D, b)   =  0.842
[[Priors]]
    T_0:            1918.50170 +/- 0.01000000
    ramp_01:        0.00000000 +/- 50.0000000
    h_1:            0.71500000 +/- 0.01100000
    h_2:            0.44200000 +/- 0.05000000
    logrho:        -1.16766000 +/- 0.07958858
    f_c:           -0.02262488 +/- 0.27596689
    f_s:            0.17534285 +/- 0.19625065
    log_S0:        -23.3738274 +/- 0.54350701
    log_omega0:     5.61297403 +/- 0.25308422
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

Roll angle versus time

Here we can check whether the approximation that $\Omega$ is contant is a good one for KELT-11. In general, variations in roll angle rate are large for stars far from the celestial equator.

In [50]:
fig,ax = subplots(nrows=2,sharex=True)
t = M.datasets[0].lc['time']
t0 = floor(t[0])
roll_angle = M.datasets[0].lc['roll_angle']
ax[0].plot(t-t0,roll_angle,'.')
ax[0].set_ylabel('Roll angle [$^{\circ}$]')
drdt = gradient(roll_angle)
Omega = np.nanmedian(drdt)
ax[1].plot(t-t0,drdt/Omega,'.')
ax[1].set_ylim(0.99,1.01)
ax[1].set_xlabel('Time')
ax[1].set_ylabel('$\Omega/<\Omega>$');

Removing roll-angle trend before fitting

There are three options for dealing with the non-linear change of roll angle with time ...

  1. Ignore it - this will be a good option if the trends are not large and/or the roll angle is nearly constant.
  2. Increase nroll.
  3. Remove the best-fit roll-angle trend determined using Dataset before fitting with MultiVisit.

To enable option 3 use unwrap=True - here is an example for KELT-11b.

In [51]:
result = M.fit_transit(log_omega0=params_prior['log_omega0'], 
                       log_S0=params_prior['log_S0'],
                       f_c=f_c, f_s=f_s, 
                       unroll=True, nroll=1, unwrap=True,
                       extra_priors={
                           'T_0':ufloat(TJD_0,0.01),
                           'h_1':star.h_1,
                           'h_2':star.h_2,
                           'logrho':star.logrho},
                       burn=512, steps=256, nwalkers=256) 
print(M.fit_report(min_correl=0.8))
Running burn-in ..
100%|██████████| 512/512 [06:40<00:00,  1.28it/s]
Running sampler ..
100%|██████████| 256/256 [03:14<00:00,  1.32it/s]
[[Fit Statistics]]
    # fitting method   = emcee
    # function evals   = 250786
    # data points      = 1493
    # variables        = 15
    chi-square         = 1634.27585
    reduced chi-square = 1.10573468
    Akaike info crit   = -21381.9138
    Bayesian info crit = -21302.2857
    RMS residual       = 167.6 ppm
[[Variables]]
    D:               0.00213327 +/- 7.3580e-05 (3.45%) (init = 0.002216312)
    W:               0.06200222 +/- 0.00144808 (2.34%) (init = 0.06188798)
    b:               0.32176623 +/- 0.15983471 (49.67%) (init = 0.4954181)
    P:               4.736525 (fixed)
    T_0:             1918.50199 +/- 6.9727e-04 (0.00%) (init = 1918.502)
    f_c:            -0.00995171 +/- 0.21253079 (2135.62%) (init = -0.02262488)
    f_s:             0.05228820 +/- 0.12292241 (235.09%) (init = 0.1753428)
    h_1:             0.71179993 +/- 0.00790440 (1.11%) (init = 0.711067)
    h_2:             0.442 (fixed)
    k:               0.04618730 == 'sqrt(D)'
    aR:              5.11062607 == 'sqrt((1+k)**2-b**2)/W/pi'
    sini:            0.99801604 == 'sqrt(1 - (b/aR)**2)'
    logrho:         -1.09781065 == 'log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)'
    e:               0.00283309 == 'f_c**2 + f_s**2'
    q_1:             0.31136400 == '(1-h_2)**2'
    q_2:             0.48351242 == '(h_1-h_2)/(1-h_2)'
    log_sigma_w:    -9.68387617 +/- 0.18050690 (1.86%) (init = -6)
    log_S0:         -23.2339345 +/- 0.25756720 (1.11%) (init = -12)
    log_omega0:      5.53431754 +/- 0.13790047 (2.49%) (init = 3)
    log_Q:          -0.3465736 (fixed)
    c_01:            1.00001116 +/- 3.5218e-05 (0.00%) (init = 0.9999968)
    dfdbg_01:        1.7630e-04 +/- 7.7243e-05 (43.81%) (init = 0.0002560888)
    dfdcontam_01:   -1.2729e-04 +/- 9.0430e-05 (71.04%) (init = -0.0001176727)
    dfdy_01:         6.1580e-05 +/- 3.1504e-05 (51.16%) (init = 8.049547e-05)
    glint_scale_01:  0.80139520 +/- 0.07908023 (9.87%) (init = 0.7369887)
[[Correlations]] (unreported correlations are < 0.800)
    C(W, f_s) =  0.904
    C(D, b)   =  0.842
[[Priors]]
    T_0:            1918.50170 +/- 0.01000000
    ramp_01:        0.00000000 +/- 50.0000000
    h_1:            0.71500000 +/- 0.01100000
    h_2:            0.44200000 +/- 0.05000000
    logrho:        -1.16766000 +/- 0.07958858
    f_c:           -0.02262488 +/- 0.27596689
    f_s:            0.17534285 +/- 0.19625065
    log_S0:        -23.3738274 +/- 0.54350701
    log_omega0:     5.61297403 +/- 0.25308422
[[Software versions]]
    CHEOPS DRP : cn01t-20200615T224519
    pycheops   : 0.9.9
    lmfit      : 1.0.1

In [52]:
M.plot_fit();
In [53]:
M.corner_plot(plotkeys='all');
In [54]:
result.params
Out[54]:
name value standard error relative error initial value min max vary expression
D 0.00213327 7.3580e-05 (3.45%) 0.0022163115674197647 5.2500e-04 0.00840000 True
W 0.06200222 0.00144808 (2.34%) 0.061887982972767604 0.01534881 0.24558088 True
b 0.32176623 0.15983471 (49.67%) 0.4954181099417319 0.00000000 1.00000000 True
P 4.73652500 (49.67%) 4.736525 0.00000000 inf False
T_0 1918.50199 6.9727e-04 (0.00%) 1918.5016091193938 1918.35504 1918.64818 True
f_c -0.00995171 0.21253079 (2135.62%) -0.022624883436623208 -1.00000000 1.00000000 True
f_s 0.05228820 0.12292241 (235.09%) 0.17534284663382985 -1.00000000 1.00000000 True
h_1 0.71179993 0.00790440 (1.11%) 0.7110669592078416 0.00000000 1.00000000 True
h_2 0.44200000 (1.11%) 0.442 0.00000000 1.00000000 False
k 0.04618730 (1.11%) None 0.00000000 1.00000000 False sqrt(D)
aR 5.11062607 (1.11%) None 1.00000000 inf False sqrt((1+k)**2-b**2)/W/pi
sini 0.99801604 (1.11%) None -inf inf False sqrt(1 - (b/aR)**2)
logrho -1.09781065 (1.11%) None -9.00000000 6.00000000 False log10(4.3275e-4*((1+k)**2-b**2)**1.5/W**3/P**2)
e 0.00283309 (1.11%) None 0.00000000 1.00000000 False f_c**2 + f_s**2
q_1 0.31136400 (1.11%) None 0.00000000 1.00000000 False (1-h_2)**2
q_2 0.48351242 (1.11%) None 0.00000000 1.00000000 False (h_1-h_2)/(1-h_2)
log_sigma_w -9.68387617 0.18050690 (1.86%) -6 -12.0000000 -2.00000000 True
log_S0 -23.2339345 0.25756720 (1.11%) -12 -30.0000000 0.00000000 True
log_omega0 5.53431754 0.13790047 (2.49%) 3 -2.30000000 8.00000000 True
log_Q -0.34657359 (2.49%) -0.34657359027997275 -inf inf False
c_01 1.00001116 3.5218e-05 (0.00%) 0.999996832127174 0.49834450 2.00222478 True
dfdbg_01 1.7630e-04 7.7243e-05 (43.81%) 0.0002560888198025411 -inf inf True
dfdcontam_01 -1.2729e-04 9.0430e-05 (71.04%) -0.00011767268668459656 -inf inf True
dfdy_01 6.1580e-05 3.1504e-05 (51.16%) 8.049547269326633e-05 -inf inf True
glint_scale_01 0.80139520 0.07908023 (9.87%) 0.7369886613196928 0.00000000 2.00000000 True
In [55]:
M.trail_plot()
Out[55]:

© Dr Pierre Maxted, Keele University, UK