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 = 0.0 # [mas]. Could use e.g. `~ LogUniform(0.00001, 10)
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)
DR4_REFERENCE_EPOCH = 2457936.875 # J2017.5
b = Planet(
name="b",
basis=Visual{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
)
gaia_ra = 294.82786250815144
gaia_dec = 14.930979608612361
sys = System(
name="gaiadr4test",
companions=[b],
likelihoods=[gaiaIADlike, rvlike],
variables=@variables begin
M_pri = 0.76
M_sec ~ LogUniform(1, 1000) # Msol
M = M_pri + M_sec
plx ~ Uniform(0.01, 100)
pmra ~ Uniform(-1000, 1000)
pmdec ~ Uniform(-1000, 1000)
# Put a prior of the catalog value +- 10,000 mas on position
# We could just fit the deltas directly, but we can propagate
# handling all non-linear effects if we know the actual ra,
# dec, and rv.
ra_offset_mas ~ Normal(0, 100)
dec_offset_mas ~ Normal(0, 100)
dec = $gaia_dec + ra_offset_mas / 60 / 60 / 1000
ra = $gaia_ra + dec_offset_mas / 60 / 60 / 1000 / cosd(dec)
ref_epoch = $DR4_REFERENCE_EPOCH
end
)
model = Octofitter.LogDensityModel(sys, verbosity=4)LogDensityModel for System gaiadr4test of dimension 17 and 642 epochs with fields .ℓπcallback and .∇ℓπcallback
We will initialize the model starting positions and visualize them:
init_chain = initialize!(model)
octoplot(model, init_chain, show_rv=true)
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=10) # might need more rounds to converge(chain = MCMC chain (1024×32×1 Array{Float64, 3}), pt = PT(checkpoint = false, ...))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)