# assist-rs: Domain layer for ASSIST + REBOUND solar-system propagation
#### A Rust crate by the Asteroid Institute, a program of the B612 Foundation
[](https://github.com/hannorein/rebound)
[](https://github.com/matthewholman/assist)<br/>
[](https://github.com/B612-Asteroid-Institute/assist-rs/actions/workflows/rust.yml)
[](https://crates.io/crates/assist-rs)
[](https://docs.rs/assist-rs)
[](LICENSE)<br/>
[](https://github.com/B612-Asteroid-Institute)
[](https://asteroid.institute/)
High-level Rust API for ephemeris-quality integration of test particles in the solar system. Wraps [ASSIST](https://github.com/matthewholman/assist) + [REBOUND](https://github.com/hannorein/rebound) and adds orbits, observatories, light-time iteration, STM propagation, and data-file management.
ASSIST uses REBOUND's IAS15 integrator (15th-order, adaptive step-size) to propagate test particle trajectories under the gravitational influence of the Sun, planets, Moon, and 16 massive asteroids, with positions sourced from JPL DE440/DE441 ephemerides.
## Crate hierarchy
```text
assist-rs ← domain types: Orbit, Origin, Observer, DataManager (this crate)
└── libassist-sys ← raw ASSIST FFI + AssistSim/Ephemeris RAII
└── librebound-sys ← raw REBOUND FFI + Simulation RAII
```
Most users want **`assist-rs`**. The `-sys` crates are useful only if you need raw FFI access or want to call REBOUND directly without ASSIST forces.
## API
### Core types
#### `Orbit`
Bundles a state vector, epoch, and optional non-gravitational parameters:
```rust
// Gravity-only orbit
let orbit = Orbit::new(
[x, y, z, vx, vy, vz], // heliocentric ecliptic J2000 (AU, AU/day)
epoch_mjd, // MJD TDB
);
// Orbit with non-gravitational forces
let ng = NonGravParams::new(0.0, 1e-10, 0.0); // A1, A2, A3
let orbit = Orbit::with_non_grav([x, y, z, vx, vy, vz], epoch_mjd, ng);
```
#### `Origin`
Identifies a solar system body or ground observatory:
```rust
Origin::Sun
Origin::Earth
Origin::Moon
Origin::JupiterBarycenter // giant planets are system barycenters
Origin::SaturnBarycenter
Origin::Observatory("I11".into()) // MPC observatory code
Origin::SolarSystemBarycenter // SSB
// Parse from string. Body names are case-insensitive; MPC codes must be
// exactly 3 alphanumeric characters. Returns Err on typos.
Origin::parse("earth")? // → Origin::Earth
Origin::parse("jupiter")? // → Origin::JupiterBarycenter
Origin::parse("W84")? // → Origin::Observatory("W84")
Origin::parse("earty").is_err() // typo → error, not silent observatory
```
### `AssistData`
Bundle of the data resources every high-level entry point needs: the JPL SPK `Ephemeris`, and optionally an `ObservatoryTable` (which itself may carry an `EarthOrientation` for sub-mas observatory rotation). Load once, pass by reference to every `assist_*` call:
```rust
let ephem = Ephemeris::from_paths(planets, asteroids)?;
// Minimal: propagation / named-body state queries only
let data = AssistData::new(ephem);
// With observatory support (required for Origin::Observatory queries and
// ground-based observers)
let data = AssistData::new(ephem).with_observatory(obs_table);
```
### `assist_propagate` / `assist_propagate_single`
N-body propagation of a test particle with optional state transition matrix (STM) via variational equations. `assist_propagate` takes many orbits and parallelizes across them; `_single` takes one orbit.
```rust
let results = assist_propagate_single(
&data,
&orbit, // Orbit (state + epoch + optional non-grav)
&[t1, t2, t3], // target epochs (MJD TDB, sorted)
true, // compute STM
)?;
// results[i].state -> [f64; 6]
// results[i].stm -> Option<[[f64; 6]; 6]> // ∂x(t)/∂x₀
// results[i].nongrav_partials -> Option<[[f64; 3]; 6]> // ∂x(t)/∂(A1,A2,A3)
```
`nongrav_partials` is populated only when `compute_stm = true` *and* the orbit carries non-gravitational parameters.
Each result can linearly propagate an initial covariance to its epoch:
```rust
// 6×6 state covariance → propagated 6×6
let p_t = results[i].propagate_covariance(&p0_6x6);
// 9×9 over (state, A1, A2, A3) → propagated 6×6 state covariance
let p_t = results[i].propagate_covariance_with_nongrav(&p0_9x9);
```
Both return `None` when the required partials are absent.
### `assist_get_state`
Query the state of any solar system body or ground observatory at one or more epochs. The fourth argument is `num_threads` (`None` = rayon global pool, `Some(1)` = serial).
```rust
let earth = assist_get_state(&data, &Origin::Earth, &[60000.0, 60001.0], Some(1))?;
// earth[0].state -> [f64; 6] heliocentric ecliptic J2000
let obs = assist_get_state(&data, &Origin::Observatory("I11".into()), &[60000.0], Some(1))?;
// `data` must have been built with `.with_observatory(..)` for this
```
### `assist_generate_ephemeris` / `assist_generate_ephemeris_single`
Propagate an orbit to observer epochs with light-time correction, returning topocentric spherical coordinates (range, RA, Dec + rates). `assist_generate_ephemeris` takes many orbits and parallelizes across them; `_single` takes one.
```rust
let results = assist_generate_ephemeris_single(
&data, // must include .with_observatory(..) for ground observatories
&orbit,
&[
Observer::new(Origin::Earth, 60010.0),
Observer::new(Origin::Observatory("I11".into()), 60011.0),
],
Some(1), // num_threads: None = global pool, Some(1) = serial
)?;
// results[i].spherical -> [rho, ra, dec, drho, dra, ddec]
// results[i].aberrated_state -> [f64; 6] light-time-corrected heliocentric
// results[i].light_time -> f64 (days)
```
### `NonGravParams`
Marsden-Sekanina non-gravitational force model with configurable g(r):
```text
a_ng = g(r) * (A1*r_hat + A2*t_hat + A3*n_hat)
g(r) = alpha * (r/r0)^(-m) * (1 + (r/r0)^n)^(-k)
```
```rust
// Default model: g(r) = r^-2 (inverse-square law)
let ng = NonGravParams::new(0.0, 1e-10, 0.0); // A1, A2, A3
// Custom model (e.g. Marsden-Sekanina water ice sublimation)
let ng = NonGravParams {
a1: 0.0, a2: 1e-10, a3: 0.0,
alpha: Some(0.111262),
nm: Some(2.15),
nn: Some(5.093),
nk: Some(4.6142),
r0: Some(2.808),
};
let orbit = Orbit::with_non_grav(state, epoch, ng);
let results = assist_propagate_single(&data, &orbit, &targets, false)?;
```
## Setup
### Ephemeris data
Requires two JPL SPK files, available from the [B612 Asteroid Institute data packages](https://b612.ai/opensource/data_packages/) or directly from JPL:
| `de440.bsp` | 114 MB | Planetary ephemeris (JPL DE440) |
| `sb441-n16.bsp` | 616 MB | 16 massive asteroid perturbers |
```rust
let ephem = Ephemeris::from_paths(
Path::new("/path/to/de440.bsp"),
Path::new("/path/to/sb441-n16.bsp"),
)?;
```
`Ephemeris` is `Send + Sync` -- load once, share across threads.
### Observatory table (optional)
For ground observatory lookups, load the MPC observatory codes from the [`mpc-obscodes`](https://pypi.org/project/mpc-obscodes/) package:
```rust
let obs_table = ObservatoryTable::from_json(
Path::new("/path/to/obscodes_extended.json"),
)?;
```
### Earth orientation
Ground-based observatory positions are rotated from ITRF93 into ICRF via a binary PCK kernel (NAIF `earth_*.bpc` files). Looking up a ground observatory without an attached kernel returns `Error::MissingEarthOrientation` — no silent fallback to a low-precision approximation.
```rust
use assist_rs::earth_orientation::EarthOrientation;
let eo = EarthOrientation::from_paths(&[
"/path/to/earth_latest_high_prec.bpc",
"/path/to/earth_620120_250826.bpc",
])?;
let obs_table = obs_table.with_earth_orientation(eo);
```
### Automatic data download (optional)
The `data` feature (on by default) provides `DataManager`, which downloads and caches everything — ephemerides, observatory codes, and Earth orientation PCKs — directly from NAIF / JPL / MPC:
```rust
use assist_rs::data::DataManager;
use assist_rs::earth_orientation::EarthOrientation;
let dm = DataManager::new(); // default: ~/.cache/assist-rs
let paths = dm.ensure_ready()?; // downloads missing files; re-fetches stale
let ephem = Ephemeris::from_paths(&paths.planets, &paths.asteroids)?;
let eo = EarthOrientation::from_paths(&paths.eop_kernels())?;
let obs_table = ObservatoryTable::from_json(&paths.obscodes)?
.with_earth_orientation(eo);
let data = AssistData::new(ephem).with_observatory(obs_table);
```
`paths.eop_kernels()` returns the three EOP kernel paths in SPICE-idiomatic order (predict, historical, current) so the high-precision kernel wins at epochs it covers.
## Building
The crate vendors REBOUND and ASSIST as git submodules and compiles them from C source via the `cc` crate. No system-level dependencies beyond a C compiler and `libm`.
```bash
git clone --recursive <repo-url>
cd assist-rs
cargo build --release
```
For **maximum performance**, build with `CC=clang`:
```bash
CC=clang cargo build --release
```
`build.rs` sets `-flto=thin` (LLVM ThinLTO), which is only picked up by clang. On clang this enables cross-translation-unit inlining of ASSIST's hot ephemeris/force-evaluation routines and yields **~6–7 % faster propagation** than the default GCC build. GCC silently ignores the flag; GCC builds are still correct and work fine.
## Testing
Tests require ephemeris data files. Set environment variables and run single-threaded to keep test output readable:
```bash
export ASSIST_PLANETS_PATH=/path/to/de440.bsp
export ASSIST_ASTEROIDS_PATH=/path/to/sb441-n16.bsp
cargo test -- --test-threads=1
```
Concurrent `AssistSim` instances on separate threads are actually safe — see the thread-safety notes on `Ephemeris` in `src/wrappers.rs`. The `--test-threads=1` flag is preserved because stdout/stderr from multiple tests interleaves badly, not because REBOUND has races we're hiding.
The Horizons validation suite (`tests/horizons_v2_test.rs`) additionally needs the observatory table and (optionally) Earth orientation kernels. Set `ASSIST_DATA_DIR` to the directory containing `obscodes_extended.json` and `earth_*.bpc` files, or use the default `~/.cache/assist-rs`.
## Versioning
`assist-rs` follows **standard Rust semver** — version reflects the Rust API, not the upstream C library. Pre-1.0 (`0.x.y`) means breaking changes can happen on any minor bump; the crate will move to 1.0.0 once the domain-layer API is settled.
This differs from the wrapped `-sys` crates ([`librebound-sys`](https://crates.io/crates/librebound-sys), [`libassist-sys`](https://crates.io/crates/libassist-sys)), whose `major.minor` mirrors the upstream C library version. The two schemes are independent: an ASSIST 1.3 release bumps `libassist-sys` to 1.3.0, but `assist-rs` only bumps if its Rust API also changes.
| 0.1.x | [1.2.0](https://github.com/matthewholman/assist/releases/tag/v1.2.0) | [4.6.0](https://github.com/hannorein/rebound/releases/tag/4.6.0) | 1.2.x | 4.6.x |
Pinned upstream versions are also recorded in `[package.metadata.vendored]` in each crate's Cargo.toml.
## License
GPL-3.0 — required by the vendored ASSIST and REBOUND sources (pulled in transitively via [`libassist-sys`](https://github.com/B612-Asteroid-Institute/libassist-sys) and [`librebound-sys`](https://github.com/B612-Asteroid-Institute/librebound-sys), each of which preserves its upstream LICENSE). See [LICENSE](LICENSE).
## References
- Holman et al. 2023, "ASSIST: An Ephemeris-Quality Test Particle Integrator", [PSJ 4 69](https://doi.org/10.3847/PSJ/acc9a9) ([arXiv:2303.16246](https://arxiv.org/abs/2303.16246))
- Rein & Liu 2012, "REBOUND: An open-source multi-purpose N-body code for collisional dynamics", [A&A 537 A128](https://doi.org/10.1051/0004-6361/201118085)
- Rein & Spiegel 2015, "IAS15: a fast, adaptive, high-order integrator for gravitational dynamics", [MNRAS 446 1424](https://doi.org/10.1093/mnras/stu2164)
## Acknowledgments
This crate is a Rust wrapper plus a domain-layer API. All credit for the underlying physics and integrator implementations belongs to the upstream projects:
- **ASSIST** — Matthew Holman and contributors. Source: <https://github.com/matthewholman/assist>. Vendored via the companion [`libassist-sys`](https://github.com/B612-Asteroid-Institute/libassist-sys) crate ([crates.io](https://crates.io/crates/libassist-sys)), which preserves the upstream LICENSE.
- **REBOUND** — Hanno Rein and contributors. Source: <https://github.com/hannorein/rebound>. Vendored via the companion [`librebound-sys`](https://github.com/B612-Asteroid-Institute/librebound-sys) crate ([crates.io](https://crates.io/crates/librebound-sys)), which preserves the upstream LICENSE.
If you use this crate in published work, please cite the ASSIST and REBOUND papers listed in [References](#references) — not this crate.
The Rust wrapper and domain-layer API are developed by the [Asteroid Institute](https://asteroid.institute/), a program of the [B612 Foundation](https://b612foundation.org/).