Basic RV Fit
You can use Octofitter to fit radial velocity data, either alone or in combination with other kinds of data. Multiple instruments (any number) are supported, as are arbitrary trends, and gaussian processes to model stellar activity.
Radial velocity modelling is supported in Octofitter via the extension package OctofitterRadialVelocity. To install it, run pkg> add OctofitterRadialVelocity
For this example, we will fit the orbit of the planet K2-131, and reproduce this RadVel tutorial.
We will use the following packages:
using Octofitter
using OctofitterRadialVelocity
using PlanetOrbits
using CairoMakie
using PairPlots
using CSV
using DataFrames
using Distributions
We will start by downloading and preparing a table of radial velocity measurements, and create a StarAbsoluteRVLikelihood
object to hold them.
The following functions allow you to directly load data from various public RV databases:
HARPS_DR1_rvs("star-name")
HARPS_RVBank_observations("star-name")
Lick_rvs("star-name")
HIRES_rvs("star-name")
Basic Fit
Start by fitting a 1-planet Keplerian model.
Calling those functions with the name of a star will return a StarAbsoluteRVLikelihood
table.
If you would like to manually specify RV data, use the following format:
rv_like = StarAbsoluteRVLikelihood(
# epoch is in units of MJD. `jd2mjd` is a helper function to convert.
# you can also put `years2mjd(2016.1231)`.
# rv and σ_rv are in units of meters/second
(epoch=jd2mjd(2455110.97985), rv=−6.54, σ_rv=1.30),
(epoch=jd2mjd(2455171.90825), rv=−3.33, σ_rv=1.09),
# ... etc.
instrument_name="insert name here"
)
For this example, to replicate the results of RadVel, we will download their example data for K2-131 and format it for Octofitter:
rv_file = download("https://raw.githubusercontent.com/California-Planet-Search/radvel/master/example_data/k2-131.txt")
rv_dat_raw = CSV.read(rv_file, DataFrame, delim=' ')
rv_dat = DataFrame();
rv_dat.epoch = jd2mjd.(rv_dat_raw.time)
rv_dat.rv = rv_dat_raw.mnvel
rv_dat.σ_rv = rv_dat_raw.errvel
tels = sort(unique(rv_dat_raw.tel))
# This table includes data from two insturments. We create a separate
# likelihood object for each:
rvlike_harps = StarAbsoluteRVLikelihood(
rv_dat[rv_dat_raw.tel .== "harps-n",:],
instrument_name="harps-n",
offset=:rv0_harps,
jitter=:jitter_harps,
)
rvlike_pfs = StarAbsoluteRVLikelihood(
rv_dat[rv_dat_raw.tel .== "pfs",:],
instrument_name="pfs",
offset=:rv0_pfs,
jitter=:jitter_pfs,
)
StarAbsoluteRVLikelihood Table with 3 columns and 31 rows:
epoch rv σ_rv
┌──────────────────────
1 │ 57828.4 -45.17 3.88
2 │ 57828.4 -36.47 4.03
3 │ 57829.2 -20.52 3.55
4 │ 57829.2 -4.58 6.07
5 │ 57830.1 17.71 3.56
6 │ 57830.1 23.34 3.48
7 │ 57830.2 21.34 3.51
8 │ 57830.2 19.92 3.26
9 │ 57830.2 -0.74 3.44
10 │ 57830.3 16.9 3.5
11 │ 57830.3 8.25 4.64
12 │ 57830.4 -4.06 4.91
13 │ 57832.1 -29.51 4.96
14 │ 57832.1 -21.56 4.11
15 │ 57832.2 -25.76 5.59
16 │ 57833.1 2.1 3.91
17 │ 57833.1 4.38 3.73
⋮ │ ⋮ ⋮ ⋮
Now, create a planet. We can use the RadialVelocityOrbit
type from PlanetOrbits.jl that requires fewer parameters (eg no inclination or longitude of ascending node). We could instead use a Visual{KepOrbit}
or similar if we wanted to include these parameters and visualize the orbit in the plane of the sky.
@planet b RadialVelocityOrbit begin
e = 0
ω = 0.0
# To match RadVel, we set a prior on Period and calculate semi-major axis from it
P ~ truncated(
Normal(0.3693038/365.256360417, 0.0000091/365.256360417),
lower=0.0001
)
a = cbrt(system.M * b.P^2) # note the equals sign.
τ ~ UniformCircular(1.0)
tp = b.τ*b.P*365.256360417 + 57782 # reference epoch for τ. Choose an MJD date near your data.
# minimum planet mass [jupiter masses]. really m*sin(i)
mass ~ LogUniform(0.001, 10)
end
@system k2_132 begin
# total mass [solar masses]
M ~ truncated(Normal(0.82, 0.02),lower=0.1) # (Baines & Armstrong 2011).
rv0_harps ~ Normal(-6693,100) # m/s
rv0_pfs ~ Normal(0,100) # m/s
jitter_harps ~ LogUniform(0.1,100) # m/s
jitter_pfs ~ LogUniform(0.1,100) # m/s
end rvlike_harps rvlike_pfs b
System model k2_132
Derived:
Priors:
M Truncated(Distributions.Normal{Float64}(μ=0.82, σ=0.02); lower=0.1)
rv0_harps Distributions.Normal{Float64}(μ=-6693.0, σ=100.0)
rv0_pfs Distributions.Normal{Float64}(μ=0.0, σ=100.0)
jitter_harps Distributions.LogUniform{Float64}(a=0.1, b=100.0)
jitter_pfs Distributions.LogUniform{Float64}(a=0.1, b=100.0)
Planet b
Derived:
e, ω, a, τ, tp,
Priors:
P Truncated(Distributions.Normal{Float64}(μ=0.001011081092683449, σ=2.4914008313533153e-8); lower=0.0001)
τy Distributions.Normal{Float64}(μ=0.0, σ=1.0)
τx Distributions.Normal{Float64}(μ=0.0, σ=1.0)
mass Distributions.LogUniform{Float64}(a=0.001, b=10.0)
Octofitter.UnitLengthPrior{:τy, :τx}: √(τy^2+τx^2) ~ LogNormal(log(1), 0.02)
StarAbsoluteRVLikelihood Table with 3 columns and 39 rows:
epoch rv σ_rv
┌─────────────────────────
1 │ 57782.2 -6682.78 3.71
2 │ 57783.1 -6710.55 5.95
3 │ 57783.2 -6698.96 8.76
4 │ 57812.1 -6672.32 4.0
5 │ 57812.2 -6672.0 4.22
6 │ 57812.2 -6690.31 4.63
7 │ 57813.0 -6701.27 4.12
8 │ 57813.1 -6700.71 5.34
9 │ 57813.1 -6695.97 3.67
10 │ 57813.1 -6705.87 4.23
11 │ 57813.1 -6694.27 5.12
12 │ 57813.2 -6699.99 5.0
13 │ 57813.2 -6696.17 10.43
14 │ 57836.0 -6675.4 7.03
15 │ 57836.0 -6665.92 7.48
16 │ 57836.0 -6661.9 6.03
17 │ 57836.1 -6657.92 5.08
⋮ │ ⋮ ⋮ ⋮StarAbsoluteRVLikelihood Table with 3 columns and 31 rows:
epoch rv σ_rv
┌──────────────────────
1 │ 57828.4 -45.17 3.88
2 │ 57828.4 -36.47 4.03
3 │ 57829.2 -20.52 3.55
4 │ 57829.2 -4.58 6.07
5 │ 57830.1 17.71 3.56
6 │ 57830.1 23.34 3.48
7 │ 57830.2 21.34 3.51
8 │ 57830.2 19.92 3.26
9 │ 57830.2 -0.74 3.44
10 │ 57830.3 16.9 3.5
11 │ 57830.3 8.25 4.64
12 │ 57830.4 -4.06 4.91
13 │ 57832.1 -29.51 4.96
14 │ 57832.1 -21.56 4.11
15 │ 57832.2 -25.76 5.59
16 │ 57833.1 2.1 3.91
17 │ 57833.1 4.38 3.73
⋮ │ ⋮ ⋮ ⋮
Note how the rvlike
object was attached to the k2_132
system instead of the planet. This is because the observed radial velocity is of the star, and is caused by any/all orbiting planets.
The rv0
and jitter
parameters specify priors for the instrument-specific offset and white noise jitter standard deviation. The _i
index matches the inst_idx
used to create the observation table.
Note also here that the mass
variable is really msini
, or the minimum mass of the planet.
We can now prepare our model for sampling.
model = Octofitter.LogDensityModel(k2_132)
LogDensityModel for System k2_132 of dimension 9 and 71 epochs with fields .ℓπcallback and .∇ℓπcallback
Sample:
using Random
rng = Random.Xoshiro(0)
chain = octofit(rng, model)
Chains MCMC chain (1000×27×1 Array{Float64, 3}):
Iterations = 1:1:1000
Number of chains = 1
Samples per chain = 1000
Wall duration = 26.69 seconds
Compute duration = 26.69 seconds
parameters = M, rv0_harps, rv0_pfs, jitter_harps, jitter_pfs, b_P, b_τy, b_τx, b_mass, b_e, b_ω, b_a, b_τ, b_tp
internals = n_steps, is_accept, acceptance_rate, hamiltonian_energy, hamiltonian_energy_error, max_hamiltonian_energy_error, tree_depth, numerical_error, step_size, nom_step_size, is_adapt, loglike, logpost, tree_depth, numerical_error
Summary Statistics
parameters mean std mcse ess_bulk ess_tail rh ⋯
Symbol Float64 Float64 Float64 Float64 Float64 Float ⋯
M 0.8196 0.0196 0.0006 1030.3595 684.0823 0.99 ⋯
rv0_harps -6693.4783 2.5758 0.0973 726.9743 424.3015 0.99 ⋯
rv0_pfs 1.6931 3.2297 0.1151 796.2978 644.1833 1.00 ⋯
jitter_harps 13.8430 1.8935 0.0796 588.6520 495.5306 1.00 ⋯
jitter_pfs 17.4532 2.5239 0.0995 643.5161 577.2825 1.00 ⋯
b_P 0.0010 0.0000 0.0000 1038.2698 804.1324 1.00 ⋯
b_τy 0.7228 0.1742 0.0062 805.8618 808.0348 1.00 ⋯
b_τx -0.6628 0.1821 0.0068 733.8291 579.6001 1.00 ⋯
b_mass 0.0416 0.0085 0.0002 1142.5047 719.8360 1.00 ⋯
b_e 0.0000 0.0000 NaN NaN NaN N ⋯
b_ω 0.0000 0.0000 NaN NaN NaN N ⋯
b_a 0.0094 0.0001 0.0000 1030.8953 684.0823 0.99 ⋯
b_τ -0.1181 0.0369 0.0014 702.1388 594.5669 1.00 ⋯
b_tp 57781.9564 0.0136 0.0005 702.1487 594.5669 1.00 ⋯
2 columns omitted
Quantiles
parameters 2.5% 25.0% 50.0% 75.0% 97.5 ⋯
Symbol Float64 Float64 Float64 Float64 Float6 ⋯
M 0.7826 0.8068 0.8193 0.8325 0.858 ⋯
rv0_harps -6698.7371 -6695.1180 -6693.4538 -6691.7607 -6688.148 ⋯
rv0_pfs -4.4870 -0.4591 1.7041 3.6608 8.055 ⋯
jitter_harps 10.7424 12.4838 13.6777 14.9815 18.090 ⋯
jitter_pfs 13.2987 15.6344 17.1543 18.8888 23.340 ⋯
b_P 0.0010 0.0010 0.0010 0.0010 0.001 ⋯
b_τy 0.3700 0.6074 0.7360 0.8511 1.029 ⋯
b_τx -0.9869 -0.7867 -0.6711 -0.5464 -0.254 ⋯
b_mass 0.0259 0.0357 0.0414 0.0473 0.058 ⋯
b_e 0.0000 0.0000 0.0000 0.0000 0.000 ⋯
b_ω 0.0000 0.0000 0.0000 0.0000 0.000 ⋯
b_a 0.0093 0.0094 0.0094 0.0095 0.009 ⋯
b_τ -0.1878 -0.1440 -0.1180 -0.0921 -0.042 ⋯
b_tp 57781.9306 57781.9468 57781.9564 57781.9660 57781.984 ⋯
1 column omitted
Excellent! Let's plot an orbit sampled from the posterior:
using CairoMakie: Makie
fig = Octofitter.rvpostplot(model, chain) # saved to "k2_132-rvpostplot.png"
We can also plot many number of samples from the posterior:
using CairoMakie: Makie
octoplot(model, chain)
And create a corner plot:
using PairPlots
using CairoMakie: Makie
octocorner(model, chain)
This example continues in Fit Gaussian Process.