Kepler Solvers

The heart of this package is being able to take a set of Keplerian elements and output relative positions, velocities, etc.

This normaly requires solving Kepler's equation numerically. This package supports a multitude of solver algorithms that can be passed to orbitsolve:

The last of these RootsMethod, allows one to substitute any algorithm from the Roots.jl package. These include many different classical and modern root finding algorithms.chosen precision, including artibrary precision BigFloats. Using big floats with, for example, Roots.PlanetOrbits.Thukral5B and a tight tolerenace, allows you to solve orbits up to arbitrary precision.

The default choice is Auto, which currently selects Markley for all cases. The Markley algorithm is very fast, reasonably accurate, and always converges, making it a good default choice.

The Markley algorithm is a tweaked version of the algorithm from AstroLib.jl. It is non-iterative and converges with less than 1e-15 relative error across the full range of e between 0 and 1. On my laptop, this solves for a single eccentric anomaly in just 71 ns. Since it is implemented in pure Julia, there is no overhead from calling into a C or Cython compiled function and no need for vectorization.

Examples

using PlanetOrbits, BenchmarkTools
orb = orbit(a=1.2, e=0.1, M=1.0, ω=1.4, τ=0.5)
t = mjd("2025-06-23")
@benchmark orbitsolve(orb, t, PlanetOrbits.Markley())
BenchmarkTools.Trial: 10000 samples with 535 evaluations.
 Range (minmax):  211.609 ns 20.987 μs   GC (min … max): 0.00% … 98.96%
 Time  (median):     215.225 ns                GC (median):    0.00%
 Time  (mean ± σ):   251.662 ns ± 493.964 ns   GC (mean ± σ):  5.46% ±  2.79%

  █▆▃ ▁          ▁                       ▂▂▅▅▅▆▅▄▃▂▂            ▂
  ████▇▆▅▅▅▄▄▄▅▇██▇▇▇▇▄▄▄▄▃▄▁▃▁▁▅▃▃▁▁▁▆██████████████▇██████▇ █
  212 ns        Histogram: log(frequency) by time        284 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.Goat())
BenchmarkTools.Trial: 10000 samples with 154 evaluations.
 Range (minmax):  689.734 ns 50.914 μs   GC (min … max): 0.00% … 98.55%
 Time  (median):     741.321 ns                GC (median):    0.00%
 Time  (mean ± σ):   738.287 ns ± 693.161 ns   GC (mean ± σ):  1.33% ±  1.39%

    █▅                      ▃                                    
  ▃███▄▃▂▂▂▂▂▂▂▂▂▁▂▁▁▂▃▄▃▂█▇▄▃▆█▆▄▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
  690 ns           Histogram: frequency by time          813 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
using Roots
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.Newton()))
BenchmarkTools.Trial: 10000 samples with 323 evaluations.
 Range (minmax):  262.347 ns 25.266 μs   GC (min … max): 0.00% … 98.81%
 Time  (median):     309.495 ns                GC (median):    0.00%
 Time  (mean ± σ):   305.894 ns ± 542.737 ns   GC (mean ± σ):  3.96% ±  2.21%

  █▄                                                             
  ██▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▃▅▃█▇▄▅▆▄▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
  262 ns           Histogram: frequency by time          348 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
using Roots
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.Thukral3B()))
BenchmarkTools.Trial: 10000 samples with 261 evaluations.
 Range (minmax):  303.364 ns 30.483 μs   GC (min … max): 0.00% … 98.90%
 Time  (median):     351.770 ns                GC (median):    0.00%
 Time  (mean ± σ):   346.789 ns ± 519.333 ns   GC (mean ± σ):  2.59% ±  1.71%

  ▄█▅▃                        ▄▃▆▆▄▄▅▄▃▃▃▁▁▁                 ▂
  █████▅▆▇▇▆▆▄▄▄▅▅▄▁▃▃▁▁▃▆█▆▅▆█████████████████▇▇█▇▆▆▇▇▆▆▆▆▆▇ █
  303 ns        Histogram: log(frequency) by time        400 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.A42()))
BenchmarkTools.Trial: 10000 samples with 110 evaluations.
 Range (minmax):  770.627 ns 73.771 μs   GC (min … max): 0.00% … 98.88%
 Time  (median):     817.527 ns                GC (median):    0.00%
 Time  (mean ± σ):   822.376 ns ± 730.320 ns   GC (mean ± σ):  0.89% ±  0.99%

  ▅█▆▁▁            ▆▇▃▇▆▄▂▁▃▅▄▂▁▁▁▂▁                         ▂
  █████▆▄▄▅▆▆▄▄▂▃▄▇█████████████████▇▇▇▆▆▆▅▆▅▇▇▆▄▅▇▇▆▄▅▅▆▆▅▅ █
  771 ns        Histogram: log(frequency) by time        923 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.Bisection()))
BenchmarkTools.Trial: 10000 samples with 10 evaluations.
 Range (minmax):  1.297 μs  4.779 μs   GC (min … max): 0.00% … 0.00%
 Time  (median):     1.306 μs                GC (median):    0.00%
 Time  (mean ± σ):   1.366 μs ± 122.867 ns   GC (mean ± σ):  0.00% ± 0.00%

  ▇█               ▄▅▅▄▃▂▂                     ▁▁    ▂
  ████▇▇▇▆▆▃▄▇▇▆▄▁▃▃▁▁▁▁▁█████████▇▇▆▅▆▄▅▆▃▅▃▅▅▅▆▅▅▆▆████▇▇ █
  1.3 μs       Histogram: log(frequency) by time      1.65 μs <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.SuperHalley()))
BenchmarkTools.Trial: 10000 samples with 313 evaluations.
 Range (minmax):  263.783 ns 25.655 μs   GC (min … max): 0.00% … 98.79%
 Time  (median):     311.989 ns                GC (median):    0.00%
 Time  (mean ± σ):   305.886 ns ± 504.814 ns   GC (mean ± σ):  3.29% ±  1.98%

  ▇█▅▂                         ▂▁▄▄▄▆▆▅▅▅▄▂▂▂▁ ▁              ▂
  ████▇▆█▇▅▄▆▅▄▅▁▃▃▁▄▅▆█▇▆▆▅▄▅▅█████████████████▇██▇▆▇▇▆▇▇▇▇▇ █
  264 ns        Histogram: log(frequency) by time        353 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.Brent()))
BenchmarkTools.Trial: 10000 samples with 8 evaluations.
 Range (minmax):  3.886 μs  7.497 μs   GC (min … max): 0.00% … 0.00%
 Time  (median):     3.936 μs                GC (median):    0.00%
 Time  (mean ± σ):   4.029 μs ± 232.658 ns   GC (mean ± σ):  0.00% ± 0.00%

  ▅▆█     ▁▃▅▅▃▁       ▁                                    ▂
  █████▇▅▃███████▇▇▅▅▄▇██▇▇▆▄▅▃▃▁▄▄▅▆▆▇▇▆▇████▇▅▃▄▅▅▆▆▆▅▇▇█ █
  3.89 μs      Histogram: log(frequency) by time      5.01 μs <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.Order2()))
BenchmarkTools.Trial: 10000 samples with 223 evaluations.
 Range (minmax):  336.955 ns 36.186 μs   GC (min … max): 0.00% … 98.90%
 Time  (median):     385.117 ns                GC (median):    0.00%
 Time  (mean ± σ):   376.690 ns ± 504.585 ns   GC (mean ± σ):  1.89% ±  1.40%

  ▆█▅▂                      ▅▃▄▆▆▄▃▄▄▃▂▁                   ▂
  █████▅▆▇▇▆▅▄▄▄▃▃▄▃▁▃▁▁▄▄▄▇██████████████████▆▇▇▆▅▇▆▆▅▆▇▆▆▆▇ █
  337 ns        Histogram: log(frequency) by time        435 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.
@benchmark orbitsolve(orb, t, PlanetOrbits.RootsMethod(Roots.AlefeldPotraShi()))
BenchmarkTools.Trial: 10000 samples with 146 evaluations.
 Range (minmax):  697.055 ns 56.776 μs   GC (min … max): 0.00% … 98.71%
 Time  (median):     744.541 ns                GC (median):    0.00%
 Time  (mean ± σ):   747.190 ns ± 778.221 ns   GC (mean ± σ):  1.47% ±  1.40%

  ▅█▅▂▂              ▂▆▅▃▇▆▄▃▃▅▅▄▂▁▂▂▁▁                       ▂
  █████▆▃▅▇▇▆▇▅▅▅▁▅▃▁██████████████████▇█▇▆▅▆▇▇▆▅▅▆▆▇▇▇▇▇▇▇▇▆ █
  697 ns        Histogram: log(frequency) by time        825 ns <

 Memory estimate: 128 bytes, allocs estimate: 1.

High precision

You can solve Kepler's equation in high precision using big floats and tightening the tolerance on the solver.

orb_big = orbit(a=big(1.2), e=big(0.1), M=big(1.0), ω=big(1.4), τ=big(0.5))
sol = orbitsolve(orb_big, big(t), PlanetOrbits.RootsMethod(Roots.Thukral5B(),rtol=1e-30,atol=1e-30,))
radvel(sol)
25030.34323194365565831507041947847102607642627168092403020601399008600937133855

Comparison