assist-rs 0.1.0

Domain layer for ASSIST + REBOUND solar-system propagation: orbits, ephemerides, observatories, STM, batch + parallel
Documentation

assist-rs: Domain layer for ASSIST + REBOUND solar-system propagation

A Rust crate by the Asteroid Institute, a program of the B612 Foundation

REBOUND 4.6.0 ASSIST 1.2.0 CI crates.io docs.rs License: GPL-3.0 GitHub Website

High-level Rust API for ephemeris-quality integration of test particles in the solar system. Wraps ASSIST + 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

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:

// 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:

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:

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.

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:

// 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).

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.

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):

a_ng = g(r) * (A1*r_hat + A2*t_hat + A3*n_hat)
g(r) = alpha * (r/r0)^(-m) * (1 + (r/r0)^n)^(-k)
// 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 or directly from JPL:

File Size Source
de440.bsp 114 MB Planetary ephemeris (JPL DE440)
sb441-n16.bsp 616 MB 16 massive asteroid perturbers
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 package:

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.

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:

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.

git clone --recursive <repo-url>
cd assist-rs
cargo build --release

For maximum performance, build with CC=clang:

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:

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, 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.

assist-rs ASSIST REBOUND libassist-sys librebound-sys
0.1.x 1.2.0 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 and librebound-sys, each of which preserves its upstream LICENSE). See LICENSE.

References

  • Holman et al. 2023, "ASSIST: An Ephemeris-Quality Test Particle Integrator", PSJ 4 69 (arXiv:2303.16246)
  • Rein & Liu 2012, "REBOUND: An open-source multi-purpose N-body code for collisional dynamics", A&A 537 A128
  • Rein & Spiegel 2015, "IAS15: a fast, adaptive, high-order integrator for gravitational dynamics", MNRAS 446 1424

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:

If you use this crate in published work, please cite the ASSIST and REBOUND papers listed in References — not this crate.

The Rust wrapper and domain-layer API are developed by the Asteroid Institute, a program of the B612 Foundation.