TOI-469: Multiplanet system with LC and RV#

We will analyse the photometry and RV data of the 3-planet system, TOI-469 as published in Damasso et al. 2023

Download data#

[ ]:
from CONAN.get_files import get_TESS_data

df = get_TESS_data("TOI-469")
df.search()
SearchResult containing 11 data products.

 #     mission     year       author      exptime target_name distance
                                             s                 arcsec
--- -------------- ---- ----------------- ------- ----------- --------
  0 TESS Sector 06 2018              SPOC     120    33692729      0.0
  1 TESS Sector 33 2020              SPOC     120    33692729      0.0
  2 TESS Sector 06 2018         TESS-SPOC    1800    33692729      0.0
  3 TESS Sector 33 2020         TESS-SPOC     600    33692729      0.0
  4 TESS Sector 06 2018               QLP    1800    33692729      0.0
  5 TESS Sector 33 2020               QLP     600    33692729      0.0
  6 TESS Sector 06 2018             TASOC     120    33692729      0.0
  7 TESS Sector 06 2018 GSFC-ELEANOR-LITE    1800    33692729      0.0
  8 TESS Sector 06 2018             TASOC    1800    33692729      0.0
  9 TESS Sector 06 2018             TASOC    1800    33692729      0.0
 10 TESS Sector 06 2018              TGLC    1800    33692729      0.0
[3]:
df.download(sectors=[6,33],author="SPOC", select_flux="pdcsap_flux",
            quality_bitmask='default')
downloaded lightcurve for sector 6
downloaded lightcurve for sector 33
[ ]:
df.scatter()
[ ]:
df.lc[6]
[6]:
df.save_CONAN_lcfile(bjd_ref = 2457000, folder="data")
saved file as: data/TOI-469_S6.dat
saved file as: data/TOI-469_S33.dat

Data Analysis#

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

import CONAN
print(f"CONAN version: {CONAN.__version__}")
CONAN version: 3.3.12
  • Transit Photometry: 2 sectors of TESS

  • RV: ESPRESSO

Setup LC object#

[2]:
path = "data/"
lc_list  = ["TOI-469_S06.dat","TOI-469_S33.dat"]

load light curve into CONAN#

[3]:
lc_obj = CONAN.load_lightcurves(file_list      = lc_list,
                                  data_filepath = path,
                                  filters       = ["T"],
                                  lamdas        = [0.8],
                                  nplanet=3)
lc_obj
load_lightcurves(): loading lightcurves from path -  data/
# ============ Input lightcurves, filters baseline function =======================================================
name            flt 𝜆_𝜇m |Ssmp ClipOutliers scl_col |off col0 col3 col4 col5 col6 col7 col8|sin id GP spline
TOI-469_S06.dat T   0.8  |None None         None    |  y    0    0    0    0    0    0    0|n    1 n  None
TOI-469_S33.dat T   0.8  |None None         None    |  y    0    0    0    0    0    0    0|n    2 n  None
[3]:
lightcurves from filepath: data/
3 transiting planet(s)
Order of unique filters: ['T']
  • The lc_obj object holds information now about the light curves. The light curves can be plotted using the plot method of the object.

By default this plots column 0 (time) against column 1 (flux) with column 3(flux err) as uncertainties.

[4]:
%matplotlib inline
[5]:
lc_obj.plot()
../_images/tutorial_TOI469_multiplanet_LC_RV_17_0.png
[6]:
lc_obj.clip_outliers(clip=4, width=15, niter=1, show_plot=True)
../_images/tutorial_TOI469_multiplanet_LC_RV_18_0.png

Planet parameters#

[7]:
traocc_pars =dict( T_0            = [(2210.4,2210.634,2210.8),    #planet 1
                                     (2207.1,2207.252,2207.5),    #planet 2
                                     (2225.1,2225.259,2225.5)],   #planet 3

                    Period        = [(13.6,13.63,13.7),
                                     (3.5,3.5379,3.6),
                                     (6.4,6.42975,6.5)],

                    Impact_para   = [(0, 0.28, 1),
                                     (0, 0.617,1),
                                     (0,0.273,1)],

                    RpRs          = [(0.001,0.0321,0.1),
                                     (0.001,0.0146,0.1),
                                     (0.001,0.0127,0.1)],

                    rho_star      = (1.42,0.1),     #same for all planets

                    K             = (0,2,10)        #m/s - unit of rv data
                  )

[8]:
lc_obj.planet_parameters(**traocc_pars)
# ============ Planet parameters (Transit and RV) setup ==========================================================
name                       fit  prior                                   note
[rho_star]/Duration        y    N(1.42,0.1)                             #choice in []|unit(gcm^-3/days)
--------repeat this line & params below for multisystem, adding '_planet_number' to the names e.g RpRs_1 for planet 1, ...
RpRs_1                     y    U(0.001,0.0321,0.1)                     #range[-0.5,0.5]
Impact_para_1              y    U(0,0.28,1)                             #range[0,2]
T_0_1                      y    U(2210.4,2210.634,2210.8)               #unit(days)
Period_1                   y    U(13.6,13.63,13.7)                      #range[0,inf]days
[Eccentricity_1]/sesinw_1  n    F(0)                                    #choice in []|range[0,1]/range[-1,1]
[omega_1]/secosw_1         n    F(90)                                   #choice in []|range[0,360]deg/range[-1,1]
K_1                        y    U(0,2,10)                               #unit(same as RVdata)
------------
RpRs_2                     y    U(0.001,0.0146,0.1)                     #range[-0.5,0.5]
Impact_para_2              y    U(0,0.617,1)                            #range[0,2]
T_0_2                      y    U(2207.1,2207.252,2207.5)               #unit(days)
Period_2                   y    U(3.5,3.5379,3.6)                       #range[0,inf]days
[Eccentricity_2]/sesinw_2  n    F(0)                                    #choice in []|range[0,1]/range[-1,1]
[omega_2]/secosw_2         n    F(90)                                   #choice in []|range[0,360]deg/range[-1,1]
K_2                        y    U(0,2,10)                               #unit(same as RVdata)
------------
RpRs_3                     y    U(0.001,0.0127,0.1)                     #range[-0.5,0.5]
Impact_para_3              y    U(0,0.273,1)                            #range[0,2]
T_0_3                      y    U(2225.1,2225.259,2225.5)               #unit(days)
Period_3                   y    U(6.4,6.42975,6.5)                      #range[0,inf]days
[Eccentricity_3]/sesinw_3  n    F(0)                                    #choice in []|range[0,1]/range[-1,1]
[omega_3]/secosw_3         n    F(90)                                   #choice in []|range[0,360]deg/range[-1,1]
K_3                        y    U(0,2,10)                               #unit(same as RVdata)

limb darkening#

[9]:
q1,q2 = lc_obj.get_LDs(Teff           = (5289,69),
                       logg           = (4.24,0.13),
                       Z              = (0.24,0.05),
                       filter_names   = ["TESS"],
                       use_result     = True)

# lc_obj.limb_darkening(q1=q1,q2=q2)
TESS      (T): q1=(0.3532, 0.0153), q2=(0.4032, 0.0118)

Setting-up limb-darkening priors from LDTk result
# ============ Limb darkening setup =============================================================================
filters fit     q1                      q2
T       y       N(0.3532,0.0153)        N(0.4032,0.0118)

add GP#

model GP as the only baseline model

get estimate rms of each light curve to use as starting point of gp amplitude

[10]:
np.array(lc_obj._rms_estimate)*1e6
[10]:
array([548.33239097, 571.47486101])
[11]:
lc_obj.add_GP(lc_list     = "all",
              par         = ["col0","col0"],
              kernel      = "mat32",
              amplitude   = (1,100, 700),    #in ppm, uses log-uniform prior
              lengthscale = (0.1,1, 20),    #in days, also log-uniform prior
              gp_pck      = "ce"
             )
# ============ Input lightcurves, filters baseline function =======================================================
name            flt 𝜆_𝜇m |Ssmp ClipOutliers scl_col |off col0 col3 col4 col5 col6 col7 col8|sin id GP spline
TOI-469_S06.dat T   0.8  |None c1:W15C4n1   None    |  n    0    0    0    0    0    0    0|n    1 ce None
TOI-469_S33.dat T   0.8  |None c1:W15C4n1   None    |  n    0    0    0    0    0    0    0|n    2 ce None
# ============ Photometry GP properties (start newline with name of * or + to Xply or add a 2nd gp to last file) =========
name/filt       kern  par    h1:[Amp]           h2:[len_scale1]    h3:[Q,η,C,α,b]     h4:[P]
all             mat32 col0   U(1,100,700)       U(0.1,1,20)        None               None

Setup RV#

[12]:
import CONAN
import matplotlib.pyplot as plt
path = "data/"

[13]:
rv_list = ["TOI469rv1.dat", "TOI469rv2.dat" ]
rv_obj = CONAN.load_rvs(file_list     = rv_list,
                         data_filepath = path,
                         nplanet       = 3,
                         rv_unit       = "m/s",
                         lc_obj        = lc_obj
                        )
rv_obj
load_rvs(): loading RVs from path - data/
# ============ Input RV curves, baseline function, GP, spline,  gamma ============================================
name          RVunit scl_col |col0 col3 col4 col5| sin GP spline_config   | gamma_m/s
TOI469rv1.dat m/s    None    |   0    0    0    0|   0 n  None            | F(0.0)
TOI469rv2.dat m/s    None    |   0    0    0    0|   0 n  None            | F(0.0)
[13]:
rvs from filepath: data/
3 planet(s)
[14]:
rv_obj.plot()
../_images/tutorial_TOI469_multiplanet_LC_RV_32_0.png
[15]:
rv_obj.rv_baseline(gamma = (81728,10))
# ============ Input RV curves, baseline function, GP, spline,  gamma ============================================
name          RVunit scl_col |col0 col3 col4 col5| sin GP spline_config   | gamma_m/s
TOI469rv1.dat m/s    None    |   0    0    0    0|   0 n  None            | N(81728,10)
TOI469rv2.dat m/s    None    |   0    0    0    0|   0 n  None            | N(81728,10)

add RV GP#

Damasso et al. used a quasiperiodic kernel in time to model stellar activity. We will multiply the exopnential-square (expsq) with a cosine (cos) kernel to acheive the quasiperiodic behaviour.

Note that mutlipling 2 kernels requires the ampltidue of the 2nd kernel to be turned off by setting it to -1

[16]:
rv_obj.add_rvGP(rv_list   = 'same',
              par         = [("col0",        "col0")],
              kernel      = [("cos",         "expsq")],
              amplitude   = [((0.1, 2, 20),    -1)],    #in same unit as rv, uses log-uniform prior
              lengthscale = [((0.01,1, 100), (0.01,1, 60))],    #in days, also log-uniform prior
              operation   = ["*"],
              gp_pck      = "ge"
             )
# ============ Input RV curves, baseline function, GP, spline,  gamma ============================================
name          RVunit scl_col |col0 col3 col4 col5| sin GP spline_config   | gamma_m/s
TOI469rv1.dat m/s    None    |   0    0    0    0|   0 ge None            | N(81728,10)
TOI469rv2.dat m/s    None    |   0    0    0    0|   0 ge None            | N(81728,10)
# ============ RV GP properties (start newline with name of * or + to Xply or add a 2nd gp to last file) =======
name          kern  par    h1:[Amp_ppm]       h2:[len_scale]     h3:[Q,η,C,α,b]     h4:[P]       | h5:[Der_Amp_ppm] ErrCol
same          cos   col0   U(0.1,2,20)        U(0.01,1,100)      None               None         | None             col2
          |*| expsq col0   F(-1)              U(0.01,1,60)       None               None         | None             col2

Setup Sampling#

[17]:
fit_obj = CONAN.fit_setup( R_st = (0.993,0.034),
                            M_st = (0.88,0.035))

fit_obj.sampling(n_cpus=10,n_live=1000)
# ============ Stellar input properties ======================================================================
# parameter     value
Radius_[Rsun]  N(0.993,0.034)
Mass_[Msun]    N(0.88,0.035)
Input_method:[R+rho(Rrho), M+rho(Mrho)]: Rrho
# ============ FIT setup =====================================================================================
Number_steps                              2000
Number_chains                             64
Number_of_processes                       10
Burnin_length                             500
n_live                                    1000
force_nlive                               False
d_logz                                    0.1
Sampler(emcee/dynesty)                    dynesty
emcee_move(stretch/demc/snooker)          stretch
nested_sampling(static/dynamic[pfrac])    static
leastsq_for_basepar(y/n)                  n
apply_LCjitter(y/n,list)                  y
apply_RVjitter(y/n,list)                  y
LCjitter_loglims(auto/[lo,hi])            auto
RVjitter_lims(auto/[lo,hi])               auto
LCbasecoeff_lims(auto/[lo,hi])            auto
RVbasecoeff_lims(auto/[lo,hi])            auto
Light_Travel_Time_correction(y/n)         n
apply_LC_GPndim_jitter(y/n)               y
apply_RV_GPndim_jitter(y/n)               y
apply_LC_GPndim_offset(y/n)               y
apply_RV_GPndim_offset(y/n)               y

Export configuration#

[18]:
CONAN.create_configfile(lc_obj, rv_obj, fit_obj,
                         filename='TOI469_lc_rvconfig.dat')
configuration file saved as TOI469_lc_rvconfig.dat

The config file can be reloaded to create all required objects to perform the fit

[1]:
import CONAN
lc_obj, rv_obj, fit_obj = CONAN.load_configfile('TOI469_lc_rvconfig.dat')
load_lightcurves(): loading lightcurves from path -  data/
load_rvs(): loading RVs from path - data/

Perform the fit#

finally perform the fitting which is saved to a results object that holds the chains of the mcmc and allows subsequent plotting

[2]:
lc_obj._fit_offset = ["y"]*lc_obj._nphot
[4]:
result = CONAN.run_fit(lc_obj,rv_obj, fit_obj,
                        out_folder="result_TOI469_multi",
                        rerun_result=True);
# ============ Input lightcurves, filters baseline function =======================================================
name            flt 𝜆_𝜇m |Ssmp ClipOutliers scl_col |off col0 col3 col4 col5 col6 col7 col8|sin id GP spline
TOI-469_S06.dat T   0.8  |None c1:W15C4n1   None    |  y    0    0    0    0    0    0    0|n    1 ce None
TOI-469_S33.dat T   0.8  |None c1:W15C4n1   None    |  y    0    0    0    0    0    0    0|n    2 ce None
# ============ Input RV curves, baseline function, GP, spline,  gamma ============================================
name          RVunit scl_col |col0 col3 col4 col5| sin GP spline_config   | gamma_m/s
TOI469rv1.dat m/s    None    |   0    0    0    0|   0 ge None            | N(81728,10)
TOI469rv2.dat m/s    None    |   0    0    0    0|   0 ge None            | N(81728,10)
# ============ Input lightcurves, filters baseline function =======================================================
name            flt 𝜆_𝜇m |Ssmp ClipOutliers scl_col |off col0 col3 col4 col5 col6 col7 col8|sin id GP spline
TOI-469_S06.dat T   0.8  |None c1:W15C4n1   None    |  y    0    0    0    0    0    0    0|n    1 ce None
TOI-469_S33.dat T   0.8  |None c1:W15C4n1   None    |  y    0    0    0    0    0    0    0|n    2 ce None
Creating output folder...result_TOI469_multi2
configuration file saved as result_TOI469_multi2/config_save.dat

================ CONAN fit launched!!! ================

Setting up photometry arrays ...
Setting up photometry GPs ...
Setting up RV arrays ...

Plotting prior distributions ...
----------------------------------

Generating initial model(s) ...
--------------------------- [0.34 secs]

Plotting initial model(s) ...
--------------------------- [14.83 secs]

Fit setup
----------
No of cpus: 10
No of dimensions: 33
fitting parameters:  ['rho_star' 'T_0_1' 'RpRs_1' 'Impact_para_1' 'Period_1' 'K_1' 'T_0_2'
 'RpRs_2' 'Impact_para_2' 'Period_2' 'K_2' 'T_0_3' 'RpRs_3'
 'Impact_para_3' 'Period_3' 'K_3' 'T_q1' 'T_q2' 'lc1_logjitter'
 'lc2_logjitter' 'rv1_gamma' 'rv1_jitter' 'rv2_gamma' 'rv2_jitter'
 'lc1_off' 'lc2_off' 'GPlc1_Amp1_col0' 'GPlc1_len1_col0' 'GPlc2_Amp1_col0'
 'GPlc2_len1_col0' 'GPrvSame_Amp1_col0' 'GPrvSame_len1_col0'
 'GPrvSame_len2_col0']

Generation of initial models completed !!!

Load result#

[1]:
import CONAN
from CONAN.utils import bin_data, phase_fold
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
[2]:
result = CONAN.load_result("result_TOI469_multi")
['lc'] Output files, ['TOI-469_S06_lcout.dat', 'TOI-469_S33_lcout.dat'], loaded into result object
load_lightcurves(): input_lc is provided, using it to load lightcurves.
['rv'] Output files, ['TOI469rv1_rvout.dat', 'TOI469rv2_rvout.dat'], loaded into result object
load_rvs(): input_rv is provided, using it to load rvs.
Linking the last created lightcurve object to the rv object for parameter linking. if this is not the related LC object, input the correct one using `lc_obj` argument of `load_rvs()`
.

LC#

[42]:
fig = result.lc.plot_bestfit(binsize=1/24)
../_images/tutorial_TOI469_multiplanet_LC_RV_51_0.png
[4]:
result.lc.names
[4]:
['TOI-469_S06.dat', 'TOI-469_S33.dat']
[5]:
#load output data files for the lc fits
lc1data = result.lc.outdata['TOI-469_S06.dat']
lc2data = result.lc.outdata['TOI-469_S33.dat']

lc1data.head()
[5]:
time flux error full_mod base_para base_sine base_spl base_gp base_total transit det_flux residual phase_1 phase_2 phase_3
0 1468.276516 1.000369 0.000546 1.000022 1.000028 0.0 1.0 -0.000007 1.000022 1.0 1.000347 0.000347 -0.461651 0.129707 0.268246
1 1468.277905 1.001143 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 1.001122 0.001122 -0.461549 0.130099 0.268462
2 1468.279293 1.000149 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 1.000128 0.000128 -0.461447 0.130492 0.268678
3 1468.280682 0.999294 0.000545 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 0.999273 -0.000727 -0.461345 0.130885 0.268894
4 1468.282071 0.999478 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 0.999457 -0.000543 -0.461243 0.131277 0.269110
[47]:
fig, ax = plt.subplots(2,1,figsize=(12,8))
ax[0].errorbar(lc1data["time"],lc1data["det_flux"], lc1data["error"],fmt=".", elinewidth=1, ecolor="gray")
ax[0].plot(lc1data.time, lc1data.transit,"r", zorder=5)
ax[0].set_ylabel("Flux");
ax[0].set_xlim([lc1data.time[0], np.array(lc1data.time)[-1]])

ax[1].errorbar(lc2data["time"],lc2data["det_flux"], lc2data["error"],c="g",fmt=".", elinewidth=1, ecolor="gray")
ax[1].plot(lc2data.time, lc2data.transit, "r",zorder=5)
ax[1].set_ylabel("Flux");
ax[1].set_xlim([lc2data.time[0], np.array(lc2data.time)[-1]])
[47]:
(2201.737329, 2227.575994)
../_images/tutorial_TOI469_multiplanet_LC_RV_54_1.png
[6]:
# join two outputs in a single dataframe (so we can plot the model across the times)
lcdata = pd.concat([lc1data,lc2data])
lcdata.head()
[6]:
time flux error full_mod base_para base_sine base_spl base_gp base_total transit det_flux residual phase_1 phase_2 phase_3
0 1468.276516 1.000369 0.000546 1.000022 1.000028 0.0 1.0 -0.000007 1.000022 1.0 1.000347 0.000347 -0.461651 0.129707 0.268246
1 1468.277905 1.001143 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 1.001122 0.001122 -0.461549 0.130099 0.268462
2 1468.279293 1.000149 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 1.000128 0.000128 -0.461447 0.130492 0.268678
3 1468.280682 0.999294 0.000545 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 0.999273 -0.000727 -0.461345 0.130885 0.268894
4 1468.282071 0.999478 0.000546 1.000021 1.000028 0.0 1.0 -0.000007 1.000021 1.0 0.999457 -0.000543 -0.461243 0.131277 0.269110
[7]:
# evaluate the transit model across both datasets and get individual planet's transit model
lcmod = result.lc.evaluate(time =np.array(lcdata["time"]), return_std=True,nsamp=1500)
print(vars(lcmod).keys())
dict_keys(['time', 'planet_model', 'components', 'sigma_low', 'sigma_high'])

Individual components#

[58]:
lc_comp    = lcmod.components
lc_comp.keys()
[58]:
dict_keys(['pl_1', 'pl_2', 'pl_3'])
[75]:
fig, ax = plt.subplots(3,1, sharex=True, gridspec_kw={'hspace':0.01},figsize=(6,12))

pl = ["pl_1", "pl_2", "pl_3", "pl_1", "pl_2"] #
cl = ["b","g","r"]
n_bin = [800, 300, 400]
for i,n in enumerate(["pl_1", "pl_2", "pl_3"]):
    phase    = phase_fold(  t   = np.array(lcdata["time"]),
                            per = result.params.P[i],
                            t0  = result.params.T0[i],
                            phase0 = -0.5)

    srt = np.argsort(phase)

    pl.remove(n)

    tr_mod          = lc_comp[n]
    subtract_signal = (lc_comp[pl[0]]-1) + (lc_comp[pl[1]]-1) #remove signal from other planets
    planet_signal   = lcdata["det_flux"] - subtract_signal

    p_bin, lc_bin, err_bin = bin_data(phase, planet_signal, lcdata["error"],bins=n_bin[i])

    ax[i].errorbar(   phase*result.params.P[i]*24, planet_signal,  lcdata["error"],
                    fmt=".",c="gray", elinewidth=0.5, alpha=0.05,
                    label=f"Planet {i+1}: {result.params_dict[f'Period_{i+1}'].n:.2f}d")

    ax[i].errorbar(p_bin*result.params.P[i]*24, lc_bin, err_bin, fmt="o",ms=5, c=cl[i], elinewidth=1)

    ax[i].plot(phase[srt]*result.params.P[i]*24, tr_mod[srt],"k",zorder=4)

    lo, hi = np.where(tr_mod==1, 1, lcmod.sigma_low), np.where(tr_mod==1, 1, lcmod.sigma_high)

    ax[i].fill_between( phase[srt]*result.params.P[i]*24,
                        lo[srt],
                        hi[srt],
                        color="cyan"
                        )

    ax[i].legend()
    ax[i].axhline(1,ls="--",c="gray", alpha=0.5)
    ax[i].set_xlabel("Hours from midtransit")
    ax[0].set_ylabel("Flux")
    ax[i].set_xlim([-3,3])
    ax[i].set_ylim([1-2*np.ptp(tr_mod), 1+2*np.ptp(tr_mod)])
    ax[i].tick_params(direction="in")


import matplotlib.ticker as ticker
def ppm_formatter(x, pos):
    """Convert flux to ppm deviation from 1"""
    return f'{(1-x)*1e6:.0f}'

# Apply to existing plot
for i in range(3):
    ax[i].yaxis.set_major_formatter(ticker.FuncFormatter(ppm_formatter))
    ax[i].set_ylabel("Flux deviation [ppm]")
../_images/tutorial_TOI469_multiplanet_LC_RV_59_0.png

RVS#

[76]:
result.rv.names
[76]:
['TOI469rv1.dat', 'TOI469rv2.dat']
[77]:
fig = result.rv.plot_bestfit()
../_images/tutorial_TOI469_multiplanet_LC_RV_62_0.png
[78]:
#load output data files for the rv fits
rv1data = result.rv.outdata['TOI469rv1.dat']
rv2data = result.rv.outdata['TOI469rv2.dat']
[79]:
#join two outputs in a single dataframe
rvdata = pd.concat([rv1data,rv2data])
rvdata.head()
[79]:
time RV error full_mod base_para base_spl base_gp base_total Rvmodel det_RV gamma residual phase_1 phase_2 phase_3
0 1765.880033 81722.91 0.807069 81723.394084 0.0 0.0 -2.225679 81726.819770 -3.425686 -3.909770 81729.045449 -0.484084 0.371466 0.246949 -0.446126
1 1792.789313 81730.59 0.593431 81730.522613 0.0 0.0 0.239247 81729.284697 1.237916 1.305303 81729.045449 0.067387 0.345614 -0.147178 -0.260984
2 1799.679952 81734.23 0.755553 81734.363394 0.0 0.0 -0.670740 81728.374709 5.988685 5.855291 81729.045449 -0.133394 -0.148867 -0.199548 -0.189298
3 1803.799572 81726.85 0.588948 81727.041896 0.0 0.0 0.436107 81729.481556 -2.439660 -2.631556 81729.045449 -0.191896 0.153361 -0.035143 0.451418
4 1806.664396 81732.28 0.628379 81731.903761 0.0 0.0 1.802835 81730.848284 1.055478 1.431716 81729.045449 0.376239 0.363533 -0.225404 -0.103023
[80]:
# evaluate the RV model across both datasets
rvmod = result.rv.evaluate(time=np.array(rvdata["time"]), return_std=True, nsamp=1500)

since the RVs are sparsely sampled, we can evaluate the RV model on a smoother time array across both datasets

[81]:
t_sm     = np.linspace(rvdata["time"].min(), rvdata["time"].max(), 2000)
rvmod_sm = result.rv.evaluate(file='TOI469rv1.dat',time=t_sm, return_std=True,nsamp=1500)
[82]:
plt.figure(figsize=(12,5))
plt.errorbar(rvdata["time"],rvdata["det_RV"], rvdata["error"],fmt="o")
plt.plot(t_sm, rvmod_sm.planet_model, alpha=0.4)
[82]:
[<matplotlib.lines.Line2D at 0x13b7ddde0>]
../_images/tutorial_TOI469_multiplanet_LC_RV_68_1.png

Individual components#

[85]:
rv_comp    = rvmod.components
rv_comp_sm = rvmod_sm.components
[89]:
fig, ax = plt.subplots(1,3, sharey=True, figsize=(17,5))

pl = ["pl_1", "pl_2", "pl_3", "pl_1", "pl_2"]
cl = ["b","g","r"]
for i,n in enumerate(["pl_1", "pl_2", "pl_3"]):
    phase    = phase_fold(  t   = np.array(rvdata["time"]),
                            per = result.params.P[i],
                            t0  = result.params.T0[i],
                            phase0 = 0)

    phase_sm = phase_fold(  t   = t_sm,
                            per = result.params.P[i],
                            t0  = result.params.T0[i],
                            phase0 = 0)

    srt = np.argsort(phase_sm)

    pl.remove(n)

    subtract_signal    = rv_comp[pl[0]]    + rv_comp[pl[1]]
    subtract_signal_sm = rv_comp_sm[pl[0]] + rv_comp_sm[pl[1]]
    planet_signal      = rvdata["det_RV"]  - subtract_signal

    p_bin, rv_bin, err_bin = bin_data(phase, planet_signal, rvdata["error"],bins=20)

    ax[i].errorbar(   phase, planet_signal,  rvdata["error"],
                    fmt=".",c="gray", elinewidth=1, alpha=0.2,
                    label=f"Planet {i+1}: {result.params_dict[f'Period_{i+1}'].n:.2f}d")

    ax[i].errorbar(p_bin, rv_bin, err_bin, fmt="o", c=cl[i], elinewidth=1)

    ax[i].plot(phase_sm[srt], rv_comp_sm[n][srt],"k",zorder=4)

    ax[i].fill_between( phase_sm[srt],
                        (rvmod_sm.sigma_low - subtract_signal_sm)[srt],
                        (rvmod_sm.sigma_high-subtract_signal_sm)[srt],
                        color="cyan"
                    )

    ax[i].legend()
    ax[i].axhline(0,ls="--",c="gray")
    ax[i].set_xlabel("Phase")
    ax[0].set_ylabel("RV [m/s]")
    ax[i].set_ylim([-5,5])
    ax[i].set_xlim([0,1])
    ax[i].tick_params(direction="in")

plt.subplots_adjust(wspace=0.015)
../_images/tutorial_TOI469_multiplanet_LC_RV_71_0.png