# Siderust
[](https://crates.io/crates/siderust)
[](https://docs.rs/siderust)
[](https://github.com/Siderust/siderust/actions/workflows/github-ci.yml)
[](LICENSE)
> **Type-safe astronomy and satellite mechanics in Rust.**
Siderust brings coordinate transformations, planetary and lunar ephemerides, time scales, and observation/orbit analysis into one Rust crate. Reference centers, frames, and physical units are represented in types, making many invalid combinations compile-time errors.
**[API documentation](https://docs.rs/siderust) · [Runnable examples](examples/README.md) · [Crates.io](https://crates.io/crates/siderust) · [Report an issue](https://github.com/Siderust/siderust/issues)**
## Quick Start
Add Siderust to a Rust project:
```bash
cargo add siderust
```
Calculate the Earth–Mars separation at the J2000 epoch using the bundled VSOP87 model:
```rust
use siderust::bodies::solar_system::{Earth, Mars};
use siderust::ephemeris::VSOP87;
use siderust::time::J2000;
fn main() {
let earth = Earth::vsop87a(J2000);
let mars = Mars::vsop87a(J2000);
println!("Earth–Mars separation at J2000: {}", earth.distance_to(&mars));
}
```
Or run a complete example from the repository:
```bash
git clone https://github.com/Siderust/siderust.git
cd siderust
cargo run --example 08_solar_system
```
No external JPL kernel is required for these VSOP87 examples. For more workflows, see the [example gallery](examples/README.md).
## Why Siderust?
- **Catch coordinate mistakes early.** Positions carry their reference center, frame, and units in Rust types; incompatible coordinate operations cannot be mixed silently.
- **Start without downloading large datasets.** Analytical VSOP87 planetary and ELP2000 lunar ephemerides are available by default; JPL DE4xx BSP kernels can be loaded separately at runtime.
- **Go beyond ephemerides.** Work with coordinate transformations, observatory sites, altitude windows, TLE/SGP4 workflows, and optional precise orbit determination.
- **Choose your environment.** Default builds use `std`; `default-features = false` supports `no_std` with `alloc` for suitable integrations.
**Scientific use:** Accuracy and coverage depend on the chosen model, epoch, time data, and reference frame. See the [tests](tests/), [dataset guidance](doc/datasets.md), and [benchmark methodology](benches/README.md) before selecting a backend for a precision-sensitive workflow.
## Documentation map
- **Getting started:** [Installation](#installation) · [Examples](#examples) · [Supported feature flags](#supported-feature-flags)
- **Core concepts:** [Features](#features) · [Coordinate systems](#coordinate-systems) · [Units and physical quantities](#units--physical-quantities)
- **Data and internals:** [Scientific archive](#scientific-archive-submodule) · [Crate layout](#crate-layout) · [Benchmarks](benches/README.md)
- **Community:** [Roadmap](#roadmap) · [Contributing](#contributing) · [License](#license) · [Acknowledgments](#acknowledgments)
---
## Supported Feature Flags
| `std` | ✔ | Standard library (filesystem, OS I/O, `Utc::now`); implies heap via always-on `alloc` |
| `serde` | ✔ | Serialization support and runtime TOML observatory catalogs (default) |
| *(base)* | | VSOP87 + ELP2000-82B analytical ephemerides, full coordinate/altitude API |
| `atmosphere` | | Atmospheric tables and radiative transfer helpers |
| `photometry` | | Photometric passbands and throughput unit (Johnson–Cousins UBVRI) |
| `spice` | | High-level SPICE kernel context (`SpiceContext`, `KernelSet`); implies `std` |
| `pod` | | Precise Orbit Determination toolkit (WLS, EKF, force models, I/O); implies `std` |
| `pod-parquet` | | Parquet residuals writer (implies `pod`) |
| `pod-doris` | | DORIS RINEX observation parser (implies `pod`) |
| `runtime-data` | | Runtime dataset-loading helpers via `siderust-archive`; implies `std` |
> **`no_std`:** use `default-features = false` for `no_std` + `alloc`. Byte-oriented
> parsers (TLE, SCK, SPICE SPK from `&[u8]`) and analytical ephemerides remain
> available; filesystem/I/O format helpers and POD require `std`.
> C ABI bindings live in the separate `siderust-ffi` crate.
> `f128` quad-precision is not supported.
---
## Features
| **Coordinate Systems** | `Position` and spherical `Direction` types parameterised by `ReferenceCenter`, `ReferenceFrame`, and `Unit`. Compile‑time guarantees prevent mixing frames by accident. |
| **Target Tracking** | `Target<T>` couples any coordinate with an observation epoch and optional `ProperMotion`, enabling extrapolation & filtering. |
| **Physical Units** | Strongly typed `Mass`, `Length`, `Angle`, `Velocity`, `Duration` & more via the [`qtty`](https://crates.io/crates/qtty) crate, dimensional correctness at compile time. |
| **Celestial Mechanics** | VSOP87 & ELP2000 theories, Pluto (Meeus/Williams), light‑time & aberration, nutation & precession, apparent Sun & Moon, culmination searches, full Vallado/AFSPC SGP4/SDP4 from TLE. |
| **Planetary / lunar orientation** | Generic `body_orientation` over IAU body, ephemeris, geocentric/topocentric center, and equatorial sky frame; Mars/Saturn conveniences; lunar bright-limb position angle. |
| **Ephemeris Backends** | Pluggable `Ephemeris` / `DynEphemeris` traits: `Vsop87Ephemeris` (always available) and [`RuntimeEphemeris`](https://docs.rs/siderust/latest/siderust/ephemeris/struct.RuntimeEphemeris.html) for JPL DE4xx BSP files at runtime. |
| **Altitude API** | Unified `AltitudeProvider` trait for Sun, Moon, stars, and arbitrary ICRS directions; find crossings, culminations, `altitude_ranges`, `above_threshold`, and `below_threshold` periods.|
| **Catalogs & Bodies** | Built‑in Sun→Neptune, asteroids (Ceres, Bennu, Apophis), comets (Halley, Encke, Hale-Bopp), a starter star catalog, typed Gaia DR3 raw/domain ingestion, + helpers for custom datasets. |
| **Starlight & Photometry** | HEALPix stellar surface-brightness maps, S10 diagnostics, passband-aware Gaia XP sampled-spectrum photon-flux integration, and typed passband-integrated stellar source records. |
| **Observatories** | TOML-backed built-in and runtime-extensible sites using one typed `Observatory` model. |
Coordinate algebra and reusable conic geometry are provided by [`affn`](https://crates.io/crates/affn); Kepler-equation solving and domain-neutral conic propagation live in [`keplerian`](https://crates.io/crates/keplerian); `siderust` adds astronomy-specific time, frame transforms, ephemeris backends, and body/observer orchestration on top.
### API Design Pillars
Siderust is built on two cross-cutting principles documented in
[`doc/conventions.md`](doc/conventions.md):
1. **Typed quantities everywhere.** Every scalar that has physical meaning —
pressures, scale heights, optical depths, airmasses, albedos, illumination
fractions, CIP coordinates — is a `qtty` newtype. Passing a raw `f64` where
a `Hectopascals` or `Kilometers` is expected is a compile-time error.
2. **Phantom-typed model selection.** Algorithm variants (e.g. nutation models)
are selected at the call site via zero-sized phantom type parameters such as
`to_frame_as::<EquatorialFrame, Iau2006A>(jd)`. There are no runtime enums
to match on. Dispatch is fully monomorphised.
### Astrometry Compliance Note
- Stellar aberration uses the full special-relativistic (Lorentz) formula per IERS Conventions (2020, §7.2); annual uses VSOP87E barycentric Earth velocity and topocentric adds a diurnal `ω×r` term.
- The Earth-orientation chain now exposes public frame transforms for `GCRS ↔ CIRS ↔ TIRS ↔ ITRF/ECEF`, plus the usual inertial and operational frames (`ICRS`, `ICRF`, `EME2000`, `TEME`, `Galactic`, planetary body-fixed).
- Local orbital frames (`RTN`, `LVLH`, `VNC`) and covariance transport in those frames are first-class via [`astro::dynamics::frames::LocalOrbitalFrame`] and [`astro::dynamics::covariance::StateCovariance`], built from a typed [`OrbitState`].
---
## Scientific Archive (submodule)
Large scientific datasets — VSOP87, IAU 2000A nutation, ELP2000-82B,
Sun-Earth Lagrange Chebyshev kernels, SPICE time/frame/constants kernels,
and dataset generators/validators — live in the separate
[`Siderust/archive`](https://github.com/Siderust/archive) repository, attached
here as the `archive/` git submodule.
After cloning Siderust, initialise the submodule:
```sh
git submodule update --init --recursive
```
All archive metadata uses **TOML** (`MANIFEST.toml`, per-family
`manifest.toml`). Binary payloads use their authoritative formats (SPICE
`.bsp`, Siderust Chebyshev Kernel `.sck`, raw `.dat`). The archive layout,
manifest schema, and regeneration recipes are documented in
[`archive/README.md`](archive/README.md) and
[`archive/schema/archive-manifest-v1.md`](archive/schema/archive-manifest-v1.md).
The default `siderust` build does **not** require a separate archive checkout.
Large scientific datasets (VSOP87, ELP2000, nutation, gravity, atmosphere, Pluto)
are embedded via the [`siderust-archive`](https://crates.io/crates/siderust-archive)
crate which is a regular Cargo dependency. JPL DE4xx kernels are resolved at
runtime from the local filesystem or downloaded on demand via `runtime-data`.
---
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
siderust = "0.15"
```
VSOP87/ELP2000 coefficients, nutation tables, and EOP data are provided by
[`siderust-archive`](https://crates.io/crates/siderust-archive) (scientific
datasets, manifests, checksums, provenance) and
[`tempoch`](https://crates.io/crates/tempoch) (UTC/TAI/TT/UT1/TDB time scales,
ΔT, and EOP freshness). Optional JPL kernels are downloaded on demand when the
corresponding feature is enabled; see `doc/datasets.md`.
### Ephemeris Backends
Siderust always includes [`Vsop87Ephemeris`](https://docs.rs/siderust/latest/siderust/ephemeris/struct.Vsop87Ephemeris.html) (VSOP87 + ELP2000-82B). Compile-time [`DefaultEphemeris`](https://docs.rs/siderust/latest/siderust/coordinates/transform/context/type.DefaultEphemeris.html) is an alias to VSOP87.
JPL DE440/DE441 (and other NAIF BSP kernels) are provided at **runtime** via [`RuntimeEphemeris`](https://docs.rs/siderust/latest/siderust/ephemeris/struct.RuntimeEphemeris.html):
```toml
[dependencies]
siderust = { version = "0.15", features = ["runtime-data"] }
```
```rust
use siderust::ephemeris::{DynEphemeris, RuntimeEphemeris};
use siderust::time::JulianDate;
let eph = RuntimeEphemeris::from_bsp("/path/to/de440.bsp")?;
let earth = eph.earth_heliocentric(JulianDate::J2000);
```
With `runtime-data`, [`siderust_archive::jpl::DatasetManager`](https://docs.rs/siderust-archive/latest/siderust_archive/jpl/struct.DatasetManager.html) can download kernels on first use (see example `12_runtime_ephemeris`).
VSOP87-only (explicit):
```toml
[dependencies]
siderust = { version = "0.15", default-features = false }
```
Combine with other features (for example `serde`):
```toml
[dependencies]
siderust = { version = "0.15", features = ["runtime-data", "serde"] }
```
### JPL datasets (runtime, not compile-time features)
DE kernels are **not** embedded at compile time. Typical files:
- `de440.bsp` (~120 MB)
- `de441_part-2.bsp` (~1.65 GB)
Prefetch into a persistent cache (optional):
```bash
export SIDERUST_DATASETS_DIR="$HOME/.cache/siderust"
# from an archive checkout:
../archive/scripts/prefetch_datasets.sh --de440
```
`cargo test --all-features` does **not** download JPL kernels by itself. Use `cargo test --features runtime-data` when exercising download paths, or point `SIDERUST_BSP_PATH` at a local BSP for integration tests (see `tests/test_jpl_real_backend.rs`).
CI sets `SIDERUST_JPL_STUB=all` for deterministic `--all-features` jobs. For local parity:
```bash
SIDERUST_JPL_STUB=all cargo test --all-features
```
Real-kernel validation (manual / optional workflow):
```bash
SIDERUST_BSP_PATH=/path/to/de440.bsp cargo test --test test_jpl_real_backend
SIDERUST_BSP_PATH=/path/to/de440.bsp cargo bench --bench de441
```
Coverage:
- Fallible JPL APIs such as `try_position`, `try_velocity`, and `try_position_velocity` return an error outside the Chebyshev segment coverage.
- The legacy infallible JPL APIs are retained for compatibility and panic with an explicit out-of-range message instead of silently extrapolating.
- Earth-orientation lookups are keyed by UTC/MJD. The default IERS provider uses `tempoch`'s bundled EOP data and reports missing coverage rather than fabricating zero EOP values. Use `NullEop` only when a documented zero-EOP approximation is intended.
---
## Coordinate Systems
Siderust encodes both the **origin** and the **orientation** of every coordinate at the type level:
```rust
use siderust::coordinates::{cartesian, centers::*, frames::*};
use qtty::Au;
// Position of Mars in the Heliocentric Ecliptic frame
let mars_helio = cartesian::Position::<Heliocentric, EclipticMeanJ2000, Au>::new(1.5, 0.0, 0.0);
```
Impossible states (e.g. adding heliocentric and geocentric positions) simply do not compile.
The public frame set includes:
- inertial / catalogue frames: `ICRS`, `ICRF`, `EquatorialMeanJ2000`, `EME2000`, `EquatorialMeanOfDate`, `EquatorialTrueOfDate`, `FK4B1950`, `Galactic`
- Earth-rotation chain frames: `GCRS`, `CIRS`, `TIRS`, `ITRF`, `ECEF`
- operational / mission frames: `TEME`, `Horizontal`, and the planetary body-fixed frames
### Compile-Time vs Runtime Safety
For most centers (`Barycentric`, `Heliocentric`, `Geocentric`) all invariants are
enforced at **compile time** with zero runtime cost (`Params = ()`).
**Parameterized centers** (`Topocentric`, `Bodycentric`) carry runtime data
(e.g., `ObserverSite`). The *center type* is still checked at compile time, but
*parameter equality* (e.g., "are these two positions at the same site?") is
checked at **runtime**:
| `pos_a - pos_b` / `distance_to` | `assert!` (panics in all builds) |
| `checked_sub` / `try_distance_to` | Returns `Err(CenterParamsMismatchError)` |
| `ObserverSite::try_new` | Validates lat/lon ranges, returns `Result` |
---
## Units & Physical Quantities
Siderust uses the [`qtty`](https://crates.io/crates/qtty) crate for dimensionally
typed quantities. The compiler prevents mixing incompatible units:
```rust
use qtty::*;
let distance = AstronomicalUnits::new(1.523); // Mars semi-major axis
let period = Days::new(686.97);
// distance + period → compile error (length + time)
```
Common unit types: `AstronomicalUnit` (`Au`), `Kilometer` (`Km`), `Meter`,
`Degree`, `Radian`, `Day`, `Second`, `AuPerDay`, and many more.
---
## Examples
The `examples/` directory is a curated tour of the crate’s major building blocks
(coordinates, transforms, altitude periods, ephemeris backends, serialization).
- Browse: [examples/README.md](examples/README.md)
- Run one: `cargo run --example 01_basic_coordinates`
Feature-gated examples:
```bash
# Runtime JPL ephemeris (BSP path argument or runtime-data download)
cargo run --example 12_runtime_ephemeris -- /path/to/de440.bsp
cargo run --features runtime-data --example 12_runtime_ephemeris
# Serde
cargo run --example 11_serde_serialization --features serde
```
## Observatory catalogs
Siderust uses one public `Observatory` model for both bundled sites and custom
sites loaded at runtime. The bundled scientific values are generated from the
canonical `data/observatories.toml` catalog and embedded at build time; no data
file lookup is needed after installation. With the default `serde` feature,
`ObservatoryCatalog::from_toml` and `ObservatoryCatalog::from_path` accept the
same `[[observatory]]` representation for user-provided catalogs.
Downstream crates should consume these Siderust records instead of maintaining
duplicate observatory coordinate registries. Site-specific calibrations remain
the downstream crate's responsibility.
## Crate Layout
```
├─ astro/ # Aberration, nutation, precession, sidereal time
├─ bodies/ # Planet, Star, Satellite, Asteroid, Comet + built-in catalogs
├─ calculus/
│ ├─ altitude/ # Unified altitude API (`AltitudeProvider` trait)
│ ├─ ephemeris/ # Ephemeris trait + VSOP87 + runtime JPL backends
│ ├─ jpl/ # Shared JPL DE4xx infrastructure (Chebyshev evaluation)
│ ├─ math_core/ # Root-finding (Brent/bisection), extrema, interval assembly
│ ├─ solar/ # Sun altitude, night/day/twilight periods
│ ├─ lunar/ # Moon altitude with topocentric parallax
│ ├─ stellar/ # Analytical star altitude engine
│ ├─ vsop87/ # VSOP87 planetary theory
│ ├─ elp2000/ # ELP2000-82B lunar theory
│ ├─ conic_equations.rs # Siderust wrappers over keplerian solvers
│ └─ pluto # Meeus/Williams Pluto ephemeris
├─ coordinates/ # Cartesian/Spherical types, frames, centers, transforms
├─ mission/ # Mission-analysis building blocks
│ ├─ geometry/ # AzElRange, Fov, TerrainMask, eclipse, orbit-relative geometry
│ ├─ context.rs # MissionContext — runtime aggregation of instruments and sites
│ └─ site.rs # Location — ground-station / observing-site metadata
├─ catalogs/ # Star catalogs and TOML-backed observatory catalog
├─ targets/ # Target<T> with time & ProperMotion
└─ time # Re-export of tempoch: JulianDate, MJD, Period<S>, time scales
```
---
## Roadmap
* [x] Custom dynamic reference centers (topocentric, bodycentric)
* [x] DE440/DE441 JPL ephemerides
* [x] Unified altitude API (`AltitudeProvider` trait)
* [x] Serde serialization support
* [x] Gaia DR3 star ingestion & cone search
* [ ] Relativistic light‑time & gravitational deflection
* [ ] Batch orbit determination helpers (LSQ & EKF)
* [ ] GPU acceleration via `wgpu` (experiment)
---
## Contributing
Contributions are welcome, including documentation fixes, runnable examples, reference-data comparisons, and bug reports. Start with the [open issues](https://github.com/Siderust/siderust/issues) or the [examples](examples/README.md).
1. Fork and clone the repository; create a feature branch.
2. Follow the [authoring conventions](doc/conventions.md) for public APIs and scientific documentation.
3. Run `cargo test && cargo clippy -- -D warnings` and add tests for behavioral changes.
4. Open a pull request describing the problem, approach, and validation performed.
For numerical discrepancies, please include the epoch, reference center/frame, units, selected model, and an independent reference when available.
By participating you agree to follow the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct).
If Siderust helps your work, [star the repository](https://github.com/Siderust/siderust) or share a reproducible example to help others discover it.
---
## License
Copyright (C) 2026 Vallés Puig, Ramon
This project is licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**.
The AGPL-3.0 ensures that:
- Any modifications and redistributions of the code (including as a network service) remain free and open.
- End users have access to the full source code, including any improvements or extensions made.
> **Note for commercial or proprietary use:**
> If you wish to incorporate this code into a closed-source or otherwise differently licensed project, a **dual-licensing** arrangement can be negotiated. Please contact the authors to discuss terms and conditions for a commercial or proprietary license that suits your needs.
---
## Acknowledgments
Big thanks to **Màrius Montón** ([@mariusmm](https://github.com/mariusmm)) for inviting me to his three-week Rust intro course at the Universitat Autònoma de Barcelona (UAB) in 2024 that nudge set this project in motion.
### Time coordinates
Siderust uses `tempoch` 0.11. `siderust::time::JulianDate` defaults to TT;
use `tempoch::JulianDate<UTC>` for civil UTC coordinates. UTC JD/MJD require
fallible conversion with a shared data snapshot:
```rust
use siderust::time::{TimeContext, Time, UTC, MJD};
use siderust::qtty::Day;
let ctx = TimeContext::new();
let utc = Time::<UTC, MJD>::try_new_with(Day::new(57754.0), &ctx)?;
let raw = utc.to_with::<MJD>(&ctx)?.try_raw_with(&ctx)?;
# Ok::<(), siderust::time::ConversionError>(())
```
MJD 57754.0 is 2017-01-01 00:00:00 UTC. Civil days have 86,400 nominal
seconds; that midnight follows 2016-12-31 midnight by 86,401 SI seconds.
Leap-second instants are valid internal instants but fail civil JD/MJD encoding.
TLE model offsets use 1,440 nominal minutes per civil day. Scalar Serde uses
civil UTC coordinates and a fresh default context; use upstream tagged split
representations for leap instants and explicit snapshots for reproducibility.
Public geometry and mechanics dependencies (`affn` 0.12, `principia` 0.7,
`keplerian`, `optica`, and related crates) resolve from crates.io without Git
patches.