Priors
All parameters to your model must have a prior defined. You may provide any continuous, univariate distribution from the Distributions.jl. A few useful distributions include:
Normal
Uniform
LogNormal
LogUniform
TrucatedNormal
VonMises
This pacakge also defines the Sine()
distribution for e.g. inclination priors and UniformCircular()
for periodic variables. Internally, UniformCircular()
creates two standard normal variables and finds the angle between them using arctan
. This allows the sampler to smoothly cycle past the ends of the domain. You can specify a different circular domain than (0,2pi) by passing the size of the domain e.g. τ = UniformCircular(1.0)
.
The VonMise distribution is notable but not commonly used. It is the analog of a normal distribution defined on a circular domain (-π, +π). If you have a Gaussian prior on an angular parameter, a Von Mises distribution is probably more appropriate.
Behind the scenes, Octofitter remaps your parameters to unconstrained domains using the Bijectors.jl (and corrects the priors accordingly). This is essential for good sampling efficiency with HMC based samplers.
This means that e.g. if you define the eccentricity prior as e=Uniform(0,0.5)
, the sampler will actually generate values across the whole real line and transform them back into the [0,0.5]
range before evaluating the orbit. It is therefore essential that your priors do not include invalid domains.
For example, setting a=Normal(3,2)
will result in poor sampling efficiency as sometimes negative values for semi-major axis will be drawn (especially if you're using the parallel tempered sampler).
Instead, for parameters like semi-major axis, eccentricity, parallax, and masses, you should truncate any distributions that have negative tails. This can easily be accomplished with TrauncatedNormal
or Trunacted(dist, low, high)
for any arbitrary distribution.
Kernel Density Estimate Priors
Octofitter has support for sampling from smoothed kernel density estimate priors. These are non-parametric distributions fit to a 1D dataset consisting of random draws. This is one way to include the output of a different model as the prior to a new model. That said, it's usually best to try and incorporate the model directly into the code. There are a few examples on GitHub of this, including atmosphere model grids, cooling tracks, etc.
Using a KDE
First, we will generate some data. In the real world, you would load this data eg. from a CSV file.
using Octofitter, Distributions
# create a smoothed KDE estimate of the samples from a 10+-1 gaussian
kde = Octofitter.KDEDist(randn(1000).+10)
KDEDist kernel density estimate distribution
Note that in Octofitter the KDE will have its support truncated to the minimum and maximum values that occur in your dataset, ie. it doesn't allow for infinite long tails.
Now add it to your model as a prior:
planet_b = Planet(
name="b",
basis=Visual{KepOrbit},
likelihoods=[],
variables=@variables begin
a ~ kde # Sample from the KDE here
e ~ Uniform(0.0, 0.99)
i ~ Sine()
M = system.M
ω ~ UniformCircular()
Ω ~ UniformCircular()
θ ~ UniformCircular()
tp = θ_at_epoch_to_tperi(θ, 50000; M, e, a, i, ω, Ω)
end
)
sys = System(
name="Tutoria",
companions=[planet_b],
likelihoods=[],
variables=@variables begin
M ~ truncated(Normal(1.2, 0.1), lower=0.1)
plx ~ truncated(Normal(50.0, 0.02), lower=0.1)
end
)
model = Octofitter.LogDensityModel(sys)
chain = octofit(model)
Chains MCMC chain (1000×29×1 Array{Float64, 3}):
Iterations = 1:1:1000
Number of chains = 1
Samples per chain = 1000
Wall duration = 0.7 seconds
Compute duration = 0.7 seconds
parameters = M, plx, b_a, b_e, b_i, b_ωx, b_ωy, b_Ωx, b_Ωy, b_θx, b_θy, b_ω, b_Ω, b_θ, b_M, 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 r ⋯
Symbol Float64 Float64 Float64 Float64 Float64 Floa ⋯
M 1.1969 0.1005 0.0033 937.7693 717.2317 1.0 ⋯
plx 49.9998 0.0191 0.0006 930.2696 563.8658 0.9 ⋯
b_a 10.0255 0.9952 0.0304 1121.6636 385.8010 0.9 ⋯
b_e 0.4899 0.2790 0.0081 1071.8791 593.4862 0.9 ⋯
b_i 1.6153 0.6870 0.0230 844.5503 552.0452 1.0 ⋯
b_ωx -0.0167 0.7128 0.0369 419.1835 730.0627 1.0 ⋯
b_ωy 0.0439 0.7109 0.0374 375.9485 657.2104 1.0 ⋯
b_Ωx 0.0326 0.7139 0.0400 389.0544 865.5821 1.0 ⋯
b_Ωy -0.0368 0.7120 0.0372 403.7078 828.2662 1.0 ⋯
b_θx -0.0248 0.7151 0.0339 485.4289 808.4078 0.9 ⋯
b_θy -0.0416 0.7059 0.0431 299.3926 936.1210 0.9 ⋯
b_ω 0.0721 1.8403 0.0870 505.6468 679.6304 1.0 ⋯
b_Ω -0.0591 1.7799 0.0814 551.6122 705.4142 1.0 ⋯
b_θ -0.0420 1.8432 0.0945 444.8333 768.4978 1.0 ⋯
b_M 1.1969 0.1005 0.0033 937.7693 717.2317 1.0 ⋯
b_tp 44409.4072 4276.0459 156.5845 683.9550 726.3208 1.0 ⋯
2 columns omitted
Quantiles
parameters 2.5% 25.0% 50.0% 75.0% 97.5%
Symbol Float64 Float64 Float64 Float64 Float64
M 1.0037 1.1344 1.1968 1.2620 1.4024
plx 49.9637 49.9869 49.9996 50.0123 50.0386
b_a 7.9866 9.3574 10.0005 10.6639 12.1013
b_e 0.0289 0.2558 0.4748 0.7269 0.9629
b_i 0.2991 1.0817 1.6502 2.1267 2.8902
b_ωx -1.0602 -0.7165 -0.0270 0.6846 1.0630
b_ωy -1.0671 -0.6215 0.0682 0.7315 1.0946
b_Ωx -1.0653 -0.6830 0.0555 0.7213 1.0641
b_Ωy -1.0893 -0.7316 -0.0923 0.6691 1.0675
b_θx -1.0467 -0.7340 -0.0597 0.6987 1.0590
b_θy -1.0557 -0.7270 -0.0735 0.6157 1.0607
b_ω -3.0121 -1.4944 0.0942 1.6973 2.9965
b_Ω -2.9818 -1.5671 -0.1612 1.4555 2.9836
b_θ -2.9383 -1.6358 -0.1039 1.5994 2.9969
b_M 1.0037 1.1344 1.1968 1.2620 1.4024
b_tp 37181.6859 40626.7649 44400.5011 48884.8245 49976.9499
We now examine the posterior and verify that it matches our KDE prior:
dat = chain[:b_a][:]
@show mean(dat) std(dat)
mean(dat) = 10.025538455031622
std(dat) = 0.9951678466281022
Observable Based Priors
Octofitter implements observable-based priors from O'Neil 2019 for relative astrometry. You can fit a model to astrometry using observable-based priors using the following recipe:
using Octofitter, Distributions
astrom_dat = Table(
epoch=[mjd("2020-12-20")],
ra=[400.0],
σ_ra=[5.0],
dec=[400.0],
σ_dec=[5.0]
)
astrom_like = PlanetRelAstromLikelihood(astrom_dat, name="rel astrom. 1")
planet_b = Planet(
name="b",
basis=Visual{KepOrbit},
likelihoods=[ObsPriorAstromONeil2019(astrom_like)],
variables=@variables begin
# For using with ObsPriors:
P ~ Uniform(0.001, 1000)
M = system.M
a = cbrt(M * P^2)
e ~ Uniform(0.0, 1.0)
i ~ Sine()
ω ~ UniformCircular()
Ω ~ UniformCircular()
mass ~ LogUniform(0.01, 100)
τ ~ UniformCircular(1.0)
tp = τ*P*365.25 + 58849 # reference epoch for τ. Choose an MJD date near your data.
end
)
sys = System(
name="System1",
companions=[planet_b],
likelihoods=[],
variables=@variables begin
plx ~ Normal(21.219, 0.060)
M ~ truncated(Normal(1.1, 0.2),lower=0.1)
end
)