Fit with a Thiele-Innes Basis

This example shows how to fit relative astrometry using a Thiele-Innes orbital basis instead of the traditional Campbell basis used in other tutorials. The Thiele-Innes basis is more suitable then Campbell for fitting low-eccentricity orbits, because it does not have the issues where ω, Ω, and tp become degenerate as eccentricity and/or inclination fall to zero.

At the end, we will convert our results back into the Campbell basis to compare.

using Octofitter
using CairoMakie
using PairPlots
using Distributions

astrom_like = PlanetRelAstromLikelihood(
    (epoch = 50000, ra = -505.7637580573554, dec = -66.92982418533026, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50120, ra = -502.570356287689, dec = -37.47217527025044, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50240, ra = -498.2089148883798, dec = -7.927548139010479, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50360, ra = -492.67768482682357, dec = 21.63557115669823, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50480, ra = -485.9770335870402, dec = 51.147204404903704, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50600, ra = -478.1095526888573, dec = 80.53589069730698, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50720, ra = -469.0801731788123, dec = 109.72870493064629, σ_ra = 10, σ_dec = 10, cor=0),
    (epoch = 50840, ra = -458.89628893460525, dec = 138.65128697876773, σ_ra = 10, σ_dec = 10, cor=0),
)
@planet b ThieleInnesOrbit begin
    e ~ Uniform(0.0, 0.5)
    A ~ Normal(0, 1000) # milliarcseconds
    B ~ Normal(0, 1000) # milliarcseconds
    F ~ Normal(0, 1000) # milliarcseconds
    G ~ Normal(0, 1000) # milliarcseconds

    θ ~ UniformCircular()
    tp = θ_at_epoch_to_tperi(system,b,50000.0)  # reference epoch for θ. Choose an MJD date near your data.
end astrom_like

@system TutoriaPrime begin
    M ~ truncated(Normal(1.2, 0.1), lower=0.1)
    plx ~ truncated(Normal(50.0, 0.02), lower=0.1)
end b

model = Octofitter.LogDensityModel(TutoriaPrime)
LogDensityModel for System TutoriaPrime of dimension 9 and 9 epochs with fields .ℓπcallback and .∇ℓπcallback

Initialize the starting points, and confirm the data are entered correcly:

init_chain = initialize!(model)
octoplot(model, init_chain)
Example block output

We now sample from the model as usual:

results = octofit(model)
Chains MCMC chain (1000×24×1 Array{Float64, 3}):

Iterations        = 1:1:1000
Number of chains  = 1
Samples per chain = 1000
Wall duration     = 4.59 seconds
Compute duration  = 4.59 seconds
parameters        = M, plx, b_e, b_A, b_B, b_F, b_G, b_θy, b_θx, 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      ⋯
      Symbol      Float64      Float64     Float64     Float64    Float64   Fl ⋯

           M       1.2107       0.0936      0.0036    681.6867   681.4476    1 ⋯
         plx      50.0003       0.0207      0.0006   1034.3689   562.6804    1 ⋯
         b_e       0.2860       0.1364      0.0083    305.4062   434.2079    0 ⋯
         b_A     100.3081     637.2560     49.0848    189.0836   424.3160    1 ⋯
         b_B    -473.6527     350.8787     35.7375    119.6351   132.9017    1 ⋯
         b_F     712.9445     587.9079     57.3751    110.2347   162.0680    1 ⋯
         b_G     186.0687     370.4414     28.8614    172.4034   333.0541    1 ⋯
        b_θy      -0.1291       0.0180      0.0007    682.7860   502.0106    0 ⋯
        b_θx      -0.9951       0.0905      0.0038    588.1140   421.9572    0 ⋯
         b_θ      -1.6998       0.0133      0.0005    672.7733   573.1526    0 ⋯
        b_tp   29373.9825   22348.6271   1452.9948    322.9448   522.9828    1 ⋯
                                                               2 columns omitted

Quantiles
  parameters          2.5%        25.0%        50.0%        75.0%        97.5% ⋯
      Symbol       Float64      Float64      Float64      Float64      Float64 ⋯

           M        1.0349       1.1467       1.2135       1.2726       1.3888 ⋯
         plx       49.9607      49.9866      49.9998      50.0129      50.0426 ⋯
         b_e        0.0297       0.1767       0.3046       0.4040       0.4882 ⋯
         b_A     -923.1253    -455.0437      89.4682     672.0951    1175.5041 ⋯
         b_B     -978.5720    -724.5082    -542.8754    -288.2079     414.4094 ⋯
         b_F     -504.5368     340.6642     709.4898    1156.0349    1819.2821 ⋯
         b_G     -494.6794    -141.3835     255.8782     521.4100     701.1710 ⋯
        b_θy       -0.1657      -0.1407      -0.1277      -0.1165      -0.0969 ⋯
        b_θx       -1.1799      -1.0513      -0.9937      -0.9343      -0.8253 ⋯
         b_θ       -1.7261      -1.7082      -1.7000      -1.6906      -1.6737 ⋯
        b_tp   -23199.8510   14752.7395   36400.3264   48361.5739   49711.3170 ⋯

Notice that the fit was very very fast! The Thiele-Innes orbital paramterization is easier to explore than the default Campbell in many cases.

We now display the results:

octoplot(model,results)
Example block output
octocorner(model, results, small=false)
Example block output

Conversion back to Campbell Elements

To convert our chain into the more familiar Campbell parameterization, we have to do a few steps. We start by turning the chain table into a an array of orbit objects, and then convert their type:

orbits_ti = Octofitter.construct_elements(model, results, :b, :) # colon means all rows
1000-element Vector{ThieleInnesOrbit{Float64}}:
 ThieleInnesOrbit{Float64}(0.30173200192322286, 11089.38799544613, 1.1645555892026769, 50.01196978471201, 1228.0710907833295, -210.8976248612639, 390.28667931501565, 549.3602852634285, 6.612251028412913, -21.97539428615863, 44297.51339279478, 0.051807274442195364, 1.365368004553625)
 ThieleInnesOrbit{Float64}(0.4031161151910368, -21936.788369273185, 1.1497879115132887, 50.028663879392695, 1288.886320800711, -310.865840732006, 1268.7765261496127, 775.8631024618461, -25.59326881695121, 21.763883647919904, 76174.55225709663, 0.03012729796824689, 1.5332108344238244)
 ThieleInnesOrbit{Float64}(0.432683662086737, -27836.975465622236, 1.1543429654916013, 50.001639599078125, 1292.9899328519589, -366.8517868699749, 1393.0114915568192, 784.0338613014454, -27.80942807721035, 21.76853619020969, 81771.7265922678, 0.028065121394469233, 1.5891412785500627)
 ThieleInnesOrbit{Float64}(0.47058693680033725, -32420.855925339536, 1.146914908041544, 50.00058672881315, 1305.4534384313718, -446.0756884708129, 1470.517996410356, 808.425405430298, -28.85025976518198, 21.615573358101376, 86023.40004041386, 0.02667801356804291, 1.6666638869326291)
 ThieleInnesOrbit{Float64}(0.13622825018729132, 24741.363367242964, 1.3565513599196766, 49.99202089755308, 138.81315571794076, -528.7289736905637, 885.0539727400256, 340.418657079319, -15.568733617354921, -1.4683355119980956, 26023.72347409261, 0.08818620577996913, 1.1469204421476813)
 ThieleInnesOrbit{Float64}(0.06574538901731933, 49221.48984676004, 1.2659276237853696, 50.01130688175469, -294.9616722597943, -515.2598514681098, 641.7131931871052, -131.7916883659473, -8.137415361870424, -5.963512965196959, 17725.992285092078, 0.12946713484567124, 1.068056201882025)
 ThieleInnesOrbit{Float64}(0.43382003007127407, 48562.50789777614, 1.236653872503406, 50.009083770980105, -727.4411976897361, -933.3875931395256, 1267.158584295372, -60.349969065181725, -19.75709421959844, -17.51551232531825, 56216.95700123098, 0.04082279717482915, 1.591365985449369)
 ThieleInnesOrbit{Float64}(0.3692023735214343, -5683.793145190408, 1.2570007966930914, 50.00526700846192, 12.819929419826263, -789.4907704104304, 1488.533088986413, 364.3227468659668, -26.574197833352656, -4.041363582887879, 55989.979161121475, 0.040988288758658946, 1.4732918838604154)
 ThieleInnesOrbit{Float64}(0.3862707255460924, -1229.9841757735703, 1.2679122774514728, 50.0085438073341, 476.370271367606, -632.9622487880799, 1363.7584979059084, 544.4556306816153, -25.193453762151258, 4.841415215865263, 52662.47403884643, 0.04357815456513662, 1.5029191092009275)
 ThieleInnesOrbit{Float64}(0.4344969965413904, -880.2203212308232, 1.1966091002765455, 50.0163220990469, 900.0732496738771, -488.3080562515471, 1122.5464699688775, 719.4434801236529, -21.13776732973069, 12.4636421583911, 53302.362403207306, 0.04305500413072223, 1.5926940723305218)
 ⋮
 ThieleInnesOrbit{Float64}(0.2563790181442335, 47809.58865975535, 1.2227337715873776, 50.027363142513664, -692.7342057844694, -490.2717593984237, 375.97477635212175, -408.47866139684146, -1.8549078883562782, -12.964356114143635, 23286.45285751037, 0.09855229765950291, 1.2998239051988416)
 ThieleInnesOrbit{Float64}(0.10175779966189354, 48087.41457837827, 1.1952215409297906, 50.00632627285656, -475.84408561730027, -390.46554463573534, 239.76060773393507, -399.2335206593711, 2.0144639008408114, -8.297531035799027, 14717.409379892786, 0.15593324709596834, 1.1075066419083297)
 ThieleInnesOrbit{Float64}(0.4135756353206301, 47894.96678194347, 1.1226272247207165, 50.00312840291843, -816.842569499015, -720.3198686293109, 528.9709800249389, -401.972754352008, -3.246305783219601, -17.56078786956417, 35623.01050511684, 0.06442278181732289, 1.5525783380207132)
 ThieleInnesOrbit{Float64}(0.2395997055678268, 48763.7041776899, 1.1763527684130841, 50.01169111218759, -443.24172669050995, -563.1911914542884, 402.6385973185827, -315.585752234424, -0.029124976429296155, -10.03632771773428, 18269.3285498344, 0.1256167366626151, 1.276790357096132)
 ThieleInnesOrbit{Float64}(0.34926976220220685, 1638.364150452413, 1.2483409649135977, 49.99956767199837, 847.7362641719818, -422.8988507454581, 1138.4858758967157, 637.4618611941098, -21.96491939230701, 12.666851005125713, 51075.01021502255, 0.044932608408413816, 1.4399549329929417)
 ThieleInnesOrbit{Float64}(0.33960827444377917, -3431.822653306728, 1.265136055108723, 49.977224312605706, 787.9150203297584, -480.8327266390711, 1296.9588240828975, 610.5697703647609, -24.87892063466236, 11.720369815318604, 56009.13145710652, 0.04097427283272323, 1.4242561359820591)
 ThieleInnesOrbit{Float64}(0.1236530096934949, 33403.18206079007, 1.1608291290648405, 50.03939881611976, 600.9381534630799, -288.25742275567137, 426.5820823626177, 458.08935416584694, -6.345830209295452, 7.822845914039328, 19212.07990967198, 0.11945262794227712, 1.1323431571448201)
 ThieleInnesOrbit{Float64}(0.24837250092931074, 27697.936771764165, 1.0713296074574084, 50.04243744652455, 536.0374714504354, -413.4109469587512, 626.8047705425837, 487.19155481770184, -9.905384689304887, 5.425427354882977, 24226.290363161825, 0.09472904844470077, 1.288756196170073)
 ThieleInnesOrbit{Float64}(0.2961925454941147, 36544.47174919262, 1.2206013065996402, 49.990939662325076, 666.6343651482977, 164.23492041265328, -196.34680506318804, 473.87354827246355, 2.2579429830739457, 9.403989442547859, 17166.683177908235, 0.1336853141438929, 1.3570874320185147)

Here is one of those entries:

display(orbits_ti[1])

We can now make a table of results (and visualize them in a corner plot) by querying properties of these objects:

table = (;
    B_a = semimajoraxis.(orbits_ti),
    B_e = eccentricity.(orbits_ti),
    B_i = rad2deg.(inclination.(orbits_ti)),
)
pairplot(table)
Example block output

We can also convert the orbit objects into Campbell parameters:

orbits_campbell = Visual{KepOrbit}.(orbits_ti)
orbits_campbell[1]
KepOrbit{Float64}
─────────────────────────
a   [au ] = 25.8
e         = 0.301732
i   [°  ] = 62.9
ω   [°  ] = 523.0
Ω   [°  ] = 178.0
tp  [day] = 11100.0
M   [M⊙ ] = 1.16 
period      [yrs ] : 121.3 
mean motion [°/yr] : 2.97 
──────────────────────────
plx [mas] = 50.0 
distance    [pc  ] : 20.0 
──────────────────────────