Zero-dependency ephemeris: JPL-derived positions and velocities for the planets and 67 moons, tested against JPL Horizons.
Project description
Orbitarium
Orbitarium is a zero-dependency Python library that computes real, error-bounded positions and velocities for the Sun's planets and 67 of their moons at any timestamp. It propagates JPL orbital elements with a Kepler solver (plus the Meeus lunar series for Earth's Moon) and is tested against pinned JPL Horizons reference states — the measured worst-case error per body is published below.
Features
- Full state vectors per body: Cartesian position x, y, z and distance r (km), velocity vx, vy, vz (km/s), and true ecliptic longitude/latitude (degrees).
- All state vectors are parent-centric (planets relative to the Sun, moons relative to their planet) in the ecliptic-J2000 axis orientation.
- 76 bodies: all eight planets, Pluto, and 67 moons — every moon with a JPL mean-elements entry, from Ganymede down to sub-kilometre Aegaeon — nested in a tree that mirrors the Solar System's structure.
- A flat, stably-ordered ML feature vector (
get_feature_vector/feature_names) for machine-learning pipelines. - Zero runtime dependencies, pure Python ≥ 3.11. No network access, no kernel files.
- Accuracy demonstrated, not claimed: an offline test suite checks every body against pinned JPL Horizons states at five epochs spanning 1900–2050.
Installation
pip install orbitarium
Quick start
import orbitarium
instance = orbitarium.Orbitarium()
tree = instance.get_positions("2026-01-01T00:00:00Z")
earth = tree["sol"]["orbitals"]["earth"]
luna = earth["orbitals"]["luna"]
print(f"Earth is {earth['r']:.0f} km from the Sun, ecliptic longitude {earth['lon']:.1f} deg")
print(f"Luna is {luna['r']:.0f} km from Earth")
get_positions accepts an ISO-8601 string (trailing Z accepted) or a datetime; naive values are interpreted as UTC. It returns a nested dictionary — every body carries the same ten keys:
{
"sol": {
"x": 0.0, "y": 0.0, "z": 0.0, "r": 0.0, # the root sits at its own origin
"vx": 0.0, "vy": 0.0, "vz": 0.0,
"lon": 0.0, "lat": 0.0,
"orbitals": {
"mercury": {...},
"venus": {...},
"earth": {
"x": -26071582.2, "y": 144774313.8, "z": -8544.2, # km, heliocentric, ecliptic-J2000
"r": 147103125.1, # km
"vx": -29.8, "vy": -5.4, "vz": 0.0, # km/s
"lon": 100.2, "lat": -0.0, # degrees, true ecliptic long/lat
"orbitals": {
"luna": {
"x": 144321.8, "y": 329397.9, "z": 31774.5, # km, geocentric
"r": 361028.2,
"vx": -1.0, "vy": 0.4, "vz": 0.0,
"lon": 66.3, "lat": 5.0,
"orbitals": {}
}
}
},
...
}
}
}
(Values above are the actual output for 2026-01-01T00:00:00Z, rounded for display.)
The ML feature vector
For machine-learning consumers there is a flat, stably-ordered interface — no tree flattening needed:
import orbitarium
instance = orbitarium.Orbitarium()
names = instance.feature_names()
vector = instance.get_feature_vector("2026-01-01T00:00:00Z")
print(len(vector)) # 684 (76 orbiting bodies x 9 quantities)
print(names[0], vector[0]) # mercury.x -32195337.59...
- Names are dotted
body.quantitypairs; bodies follow the catalog's depth-first order, and each body contributesx, y, z, r, vx, vy, vz, lon, latin that order. - Only time-varying quantities appear — no physical constants, and the Sun contributes nothing.
- Values are raw physical units (km, km/s, degrees); normalization is deliberately the consumer's job.
- The ordering is a public contract from v2.0.0 on: future minor versions may only append new features at the end; reordering or removal requires a major version.
Accuracy
Measured worst-case deviation from JPL Horizons over five pinned epochs spanning 1900–2050 (angular separation of the position vector, relative distance error, relative velocity-vector error). The test suite enforces tolerances of roughly twice these values; regenerate the table with python tools/measure_accuracy.py --markdown.
| Body | Worst angle | Worst distance | Worst velocity | Epochs |
|---|---|---|---|---|
| mercury | 28″ | 0.002% | 0.01% | 5 |
| venus | 13″ | 0.002% | 0.01% | 5 |
| earth | 6″ | 0.003% | 0.05% | 5 |
| luna | 52″ | 0.002% | 0.02% | 5 |
| mars | 57″ | 0.015% | 0.02% | 5 |
| phobos | 2.57° | 1.036% | 3.06% | 5 |
| deimos | 0.39° | 0.010% | 0.70% | 5 |
| jupiter | 309″ | 0.070% | 0.16% | 5 |
| metis | 1.07° | 0.078% | 1.78% | 5 |
| adrastea | 1.02° | 0.062% | 1.73% | 5 |
| amalthea | 0.68° | 0.258% | 1.56% | 5 |
| thebe | 2.44° | 1.854% | 2.66% | 5 |
| io | 0.52° | 0.440% | 0.90% | 5 |
| europa | 0.83° | 0.948% | 1.37% | 5 |
| ganymede | 0.18° | 0.109% | 0.29% | 5 |
| callisto | 223″ | 0.014% | 0.12% | 5 |
| saturn | 0.15° | 0.132% | 0.38% | 5 |
| pan | 0.51° | 0.001% | 0.88% | 4 |
| daphnis | 0.52° | 0.001% | 0.91% | 1 |
| atlas | 9.43° | 0.270% | 16.23% | 4 |
| prometheus | 16.57° | 0.211% | 28.70% | 4 |
| pandora | 27.84° | 0.499% | 47.79% | 4 |
| janus | 8.95° | 0.580% | 15.10% | 4 |
| epimetheus | 25.28° | 2.398% | 43.33% | 4 |
| aegaeon | 3.35° | 0.072% | 5.84% | 4 |
| mimas | 4.31° | 1.310% | 5.13% | 5 |
| methone | 5.36° | 0.270% | 9.35% | 5 |
| anthe | 11.89° | 0.399% | 20.64% | 4 |
| pallene | 0.89° | 0.285% | 1.27% | 4 |
| enceladus | 0.63° | 0.014% | 1.11% | 5 |
| tethys | 0.19° | 0.014% | 0.32% | 5 |
| telesto | 2.60° | 0.020% | 4.54% | 5 |
| calypso | 2.61° | 0.072% | 4.58% | 5 |
| dione | 0.16° | 0.018% | 0.27% | 5 |
| helene | 9.49° | 1.367% | 16.56% | 5 |
| polydeuces | 0.53° | 0.076% | 0.89% | 5 |
| rhea | 353″ | 0.017% | 0.18% | 5 |
| titan | 206″ | 0.017% | 0.08% | 5 |
| hyperion | 2.24° | 3.247% | 4.11% | 5 |
| iapetus | 0.11° | 0.062% | 0.20% | 5 |
| phoebe | 2.34° | 1.896% | 2.77% | 5 |
| uranus | 79″ | 0.043% | 0.27% | 5 |
| cordelia | 0.91° | 0.038% | 1.54% | 5 |
| ophelia | 2.33° | 0.879% | 2.75% | 5 |
| bianca | 1.43° | 0.709% | 1.89% | 5 |
| cressida | 0.97° | 0.474% | 1.42% | 5 |
| desdemona | 1.24° | 0.668% | 1.67% | 5 |
| juliet | 0.88° | 0.592% | 1.30% | 5 |
| portia | 0.84° | 0.333% | 1.22% | 5 |
| rosalind | 0.67° | 0.264% | 1.10% | 5 |
| cupid | 1.17° | 0.481% | 1.43% | 5 |
| belinda | 0.49° | 0.198% | 0.84% | 5 |
| perdita | 0.74° | 0.329% | 1.10% | 5 |
| puck | 1.31° | 0.878% | 1.43% | 5 |
| mab | 0.80° | 0.499% | 1.03% | 5 |
| miranda | 0.69° | 0.046% | 1.20% | 5 |
| ariel | 0.24° | 0.120% | 0.41% | 5 |
| umbriel | 318″ | 0.104% | 0.13% | 5 |
| titania | 0.16° | 0.096% | 0.24% | 5 |
| oberon | 0.14° | 0.121% | 0.21% | 5 |
| neptune | 45″ | 0.022% | 0.30% | 5 |
| naiad | 1.25° | 0.016% | 2.13% | 5 |
| thalassa | 1.12° | 0.020% | 2.03% | 5 |
| despina | 0.88° | 0.027% | 1.53% | 5 |
| galatea | 0.69° | 0.017% | 1.20% | 5 |
| larissa | 0.77° | 0.083% | 1.06% | 5 |
| hippocamp | 0.36° | 0.014% | 0.87% | 5 |
| proteus | 0.58° | 0.045% | 0.78% | 5 |
| triton | 0.25° | 0.000% | 0.36% | 5 |
| nereid | 4.99° | 4.771% | 5.55% | 5 |
| pluto | 39″ | 0.023% | 0.59% | 5 |
| charon | 178″ | 0.000% | 0.09% | 5 |
| styx | 0.56° | 0.331% | 1.46% | 5 |
| nix | 0.29° | 0.337% | 0.90% | 5 |
| kerberos | 0.25° | 0.359% | 0.61% | 5 |
| hydra | 0.11° | 0.033% | 0.87% | 5 |
(Bodies whose satellite ephemeris does not reach back to 1900 are pinned at fewer epochs — Daphnis's only spans 1990–2018.)
In short: planets are good to arcseconds–arcminutes, Luna to under an arcminute, and most moons to a degree or better. The exceptions are physical, not numerical: the co-orbital and chaotic Saturnian moons — the Janus–Epimetheus horseshoe pair (which swap orbits every ~4 years; a two-harmonic libration can only approximate the sawtooth), the formally chaotic Prometheus–Pandora–Atlas group, the Dione trojan Helene, and the Mimas-resonant Anthe — carry worst-case errors of ~9–28°, and Mimas (~44° physical libration), Nereid (a 360-day, e = 0.75 orbit strongly perturbed by the Sun), Methone, and Phobos (Mars J2 short-period terms) sit in the 2–5° range. Every one of these is bounded by the test suite, so a regression past the documented worst case fails CI.
Validity window: 1800–2050. Timestamps outside it still compute but emit a UserWarning — the element fits degrade gradually beyond their fit spans.
How it works
- Planets and Pluto — E. M. Standish's "Keplerian Elements for Approximate Positions of the Major Planets" (JPL): J2000 mean elements plus secular rates, valid 1800–2050, solved with Newton iteration on Kepler's equation and rotated into 3D ecliptic-J2000 states.
- Luna — the standard Meeus truncated lunar series (embedded periodic-term tables for longitude, latitude, and distance), accurate to well under an arcminute against Horizons; far beyond what mean elements can do for Earth's strongly perturbed Moon.
- Moons — precessing-ellipse mean elements fit by least squares to JPL Horizons states over 1900–2050 (the committed, reproducible fit tool lives in
tools/fit_satellite_elements.py). Reference-plane poles and ephemeris IDs come from JPL's Planetary Satellite Mean Elements page; the ellipses themselves are refit because the published table — as its own page warns — is not intended for ephemeris computation (its angles disagree with Horizons by up to ~157° at their own epoch for the Saturnian moons). Physically motivated extra terms are included where a plain ellipse cannot follow the real orbit: explicit mean-longitude libration terms for resonant, trojan, and co-orbital moons (Mimas–Tethys, Titan–Hyperion, the Janus–Epimetheus horseshoe pair, the Tethys and Dione trojans, the small Mimas-resonant moons — which additionally inherit Mimas's 71.8-yr resonance cycle as a second libration frequency — and the chaotic Prometheus–Pandora pair), Phobos's tidal mean-motion acceleration, and barycentric-wobble corrections for Pluto's small moons. - Retrograde orbits (Triton) are represented physically, via inclination > 90°; there are no negative periods.
- Timescale — inputs are UTC, used directly where Terrestrial Time is formally required. The ~69 s difference contributes at most ~0.9° for the fastest body (Phobos) and is negligible for everything else; the accuracy table above already includes this simplification, since the reference fixtures are pinned at exact TDB instants.
- Physical constants — masses, mean radii, and GM values for every body are available as proper floats with cited JPL sources in
orbitarium.data.physical.PHYSICAL; catalog integrity (required fields, numeric types, sanity ranges, citations) is enforced byorbitarium.data.catalog.validate()in CI.
The reference fixtures (five epochs per body, generated once from the JPL Horizons API by tools/generate_horizons_fixtures.py) are committed to the repository, so the full test suite runs offline.
Migrating from v1
v1 positions were not physical — a single 0–360° phase angle computed from an arbitrary epoch in the year 2492. v2 is a clean break: real states, ten numeric fields per body, and the legacy position field is gone. get_positions keeps its nested tree shape, and max_range is still accepted but deprecated (it emits a DeprecationWarning and has no effect). See CHANGELOG.md for the full list of changes and migration notes.
Contributing
Contributions, bug reports, and feature requests are welcome! Please open an issue or submit a pull request on the GitHub repository.
License
Orbitarium is licensed under the GNU General Public License v3.0. See the LICENSE file for more details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file orbitarium-2.0.0.tar.gz.
File metadata
- Download URL: orbitarium-2.0.0.tar.gz
- Upload date:
- Size: 65.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c19b4ad71d5467cd486f809f779ca71fe0040e678f0dda4ebeea857b93c1361
|
|
| MD5 |
dbcfcc928cdf909d6b0193f47858e537
|
|
| BLAKE2b-256 |
2d00c31443af44acec63eaa088a333ba965af67ffba5a814e1f4a0adc0a9d215
|
File details
Details for the file orbitarium-2.0.0-py3-none-any.whl.
File metadata
- Download URL: orbitarium-2.0.0-py3-none-any.whl
- Upload date:
- Size: 53.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a67d34a5610563e3800ec4f55a57338b9629da20c1a9b92701b221fe1052f7f
|
|
| MD5 |
47a07ba5fbbfdc4d469396d525b8cc0a
|
|
| BLAKE2b-256 |
6a438f2b16153aa8ea4f39d9facac7809db91916c7f7190f086ee03268f3aff5
|