Fitting Gaia DR4 IAD
This tutorial shows how you can use Octofitter to fit preliminary RV and absolute astrometry data from DR4, using the already published data for the Gaia-BH3 black hole.
The final format of the Gaia IAD data may change, in which case this tutorial will be updated.
using CairoMakie
using Octofitter
using Distributions
using CSV, DataFramesAs a first step, we will load the astrometry data for Gaia-BH3 and plot it:
headers = [
:transit_id
:ccd_id
:obs_time_tcb
:centroid_pos_al
:centroid_pos_error_al
:parallax_factor_al
:scan_pos_angle
:outlier_flag
]
df = CSV.read(joinpath(@__DIR__, "astrom.dat"), DataFrame, skipto=7, header=headers, delim=' ', ignorerepeated=true)
df.epoch = jd2mjd.(df.obs_time_tcb)
scatter(
df.obs_time_tcb,
df.centroid_pos_al,
)
We can now construct a likelihood object for this data. We must also supply the Gaia ID, which will be used to query the full Gaia solution for this object (for now, using DR3):
gaiaIADlike = GaiaDR4Astrom(
df,
gaia_id=4318465066420528000,
variables=@variables begin
astrometric_jitter ~ LogUniform(0.00001, 10) # mas
end
)GaiaDR4Astrom Table with 9 columns and 622 rows:
transit_id ccd_id obs_time_tcb centroid_pos_al ⋯
┌────────────────────────────────────────────────────────────
1 │ 20114916805338633 1 2.45696e6 147.066 ⋯
2 │ 20114916805338633 2 2.45696e6 146.696 ⋯
3 │ 20114916805338633 3 2.45696e6 146.685 ⋯
4 │ 20114916805338633 4 2.45696e6 146.557 ⋯
5 │ 20114916805338633 5 2.45696e6 146.396 ⋯
6 │ 20114916805338633 6 2.45696e6 146.374 ⋯
7 │ 20114916805338633 7 2.45696e6 146.436 ⋯
8 │ 20114916805338633 8 2.45696e6 146.06 ⋯
9 │ 20114916805338633 9 2.45696e6 146.256 ⋯
10 │ 20119009095238725 1 2.45696e6 146.244 ⋯
11 │ 20119009095238725 2 2.45696e6 146.148 ⋯
12 │ 20119009095238725 3 2.45696e6 146.192 ⋯
13 │ 20119009095238725 4 2.45696e6 146.271 ⋯
14 │ 20119009095238725 5 2.45696e6 146.047 ⋯
15 │ 20119009095238725 6 2.45696e6 146.068 ⋯
16 │ 20119009095238725 7 2.45696e6 146.023 ⋯
17 │ 20119009095238725 8 2.45696e6 146.095 ⋯
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋱This object also has published RV data from Gaia, which we can load and use as normal:
using CSV, DataFrames
using OctofitterRadialVelocity
headers_rv = [
:transit_id,
:obs_time_tcb,
:radial_velocity_kms,
:radial_velocity_err_kms,
]
dfrv = CSV.read(joinpath(@__DIR__, "epochrv.dat"), DataFrame, skipto=7, header=headers_rv, delim=' ', ignorerepeated=true)
dfrv.epoch = jd2mjd.(dfrv.obs_time_tcb)
dfrv.rv = dfrv.radial_velocity_kms * 1e3
dfrv.σ_rv = dfrv.radial_velocity_err_kms * 1e3
# Calculate mean RV for the prior
mean_rv = mean(dfrv.rv)
rvlike = StarAbsoluteRVLikelihood(
dfrv,
name="GaiaRV",
variables=@variables begin
offset ~ Normal(mean_rv, 10_000) # wide prior on RV offset centred on mean RV
jitter ~ LogUniform(0.01, 100_000) # RV jitter parameter
end
)
errorbars(
dfrv.obs_time_tcb,
dfrv.rv,
dfrv.σ_rv
)
Now, we define a model that incorporates this data:
mjup2msol = Octofitter.mjup2msol
ref_epoch_mjd = Octofitter.meta_gaia_DR3.ref_epoch_mjd
orbit_ref_epoch = mean(gaiaIADlike.table.epoch)
b = Planet(
name="BH",
basis=AbsoluteVisual{KepOrbit},
likelihoods=[],
variables=@variables begin
a ~ Uniform(0, 1000)
e ~ Uniform(0, 0.99)
ω ~ UniformCircular()
i ~ Sine()
Ω ~ UniformCircular()
θ ~ UniformCircular()
tp = θ_at_epoch_to_tperi(θ, $orbit_ref_epoch; M=system.M, e, a, i, ω, Ω)
mass = system.M_sec / mjup2msol
end
)
# DR3 catalog position and reference epoch
gaia_ra = 294.82786250815144
gaia_dec = 14.930979608612361
ref_epoch_mjd = Octofitter.meta_gaia_DR3.ref_epoch_mjd
sys = System(
name="gaiadr4test",
companions=[b],
likelihoods=[gaiaIADlike, rvlike,],
variables=@variables begin
M_pri ~ truncated(Normal(0.76,0.05),lower=0.1) # M sun
M_sec ~ LogUniform(1, 1000) # M sun
M = M_pri + M_sec
# Note: keep these physically plausible to prevent numerical errors
plx ~ Uniform(0.01,100) # mas
pmra ~ Uniform(-1000, 1000) # mas/yr
pmdec ~ Uniform(-1000, 1000) # mas/yr
rv = −333.2e3 # m/s
# Put a prior of the catalog value +- 10,000 mas on position
ra_offset_mas ~ Normal(0, 10000)
dec_offset_mas ~ Normal(0, 10000)
dec = $gaia_dec + ra_offset_mas / 60 / 60 / 1000
ra = $gaia_ra + dec_offset_mas / 60 / 60 / 1000 / cosd(dec)
# Important! This is the reference epoch for the ra and dec provided above, *not* necessarily DR4.
ref_epoch = $ref_epoch_mjd
end
)
model = Octofitter.LogDensityModel(sys, verbosity=4)LogDensityModel for System gaiadr4test of dimension 19 and 642 epochs with fields .ℓπcallback and .∇ℓπcallback
We will initialize the model starting positions and visualize them:
# Note: you can see the required format for paramter initialization by running:
# nt = Octofitter.drawfrompriors(model.system);
# println(nt)
# then remove any derived parameters (parameters in your model that are on the right of an `=`)
init_chain = initialize!(model, (;
M_pri = 0.7792923132247755,
M_sec = 36.032664849109906,
plx = 1.6686144513164856,
pmra = -27.89740759925553,
pmdec = -156.1023951519146,
ra_offset_mas = 236.8072112885035,
dec_offset_mas = 45.781075653307376,
observations = (GaiaDR4 = (astrometric_jitter = 0.027554101045898238,),
GaiaRV = (offset = -359481.6706770764,jitter = 2143.4793485877644)),
planets = (BH = (
a = 18.905647598089196,
e = 0.7583328001601555,
ωx = -0.19433584569119122,
ωy = -0.9414842877197981,
i = 1.9216027029499319,
Ωx = -0.9890745570284801,
Ωy = 0.9268637554445821,
θx = 0.4250634152645573,
θy = -0.19794636356747858,
),
)); verbosity=4)
octoplot(model, init_chain, show_rv=true)
If you don't pick the starting point, you cabn also just run Pigeons for 8-10 rounds, which is recommended anyways for convergence, and the sampler will find this result.
Now, we can perform the fit. It is a little slow since we have many hundreds of RV and astrometry data points.
using Pigeons
chain, pt = octofit_pigeons(model, n_rounds=6) # might need more rounds to converge(chain = MCMC chain (64×33×1 Array{Float64, 3}), pt = PT(checkpoint = false, ...))increment_n_rounds!(pt,1)
chain,pt = octofit_pigeons(pt)Finally, we can visualize the results:
octoplot(model, chain, show_rv=true, mark_epochs_mjd=mjd.([
"2017"
"2022"
"2027"
]))
octocorner(model, chain, small=true)
Octofitter.rvpostplot(model, chain)