empyrean-sys 0.9.0

Low-level FFI bindings to the libempyrean astrodynamics C ABI. For an ergonomic Rust API, use the empyrean crate.
Documentation
<img src="https://raw.githubusercontent.com/Empyrean-Dynamics/empyrean/main/docs/empyrean-dynamics-icon.png" width="140" alt="empyrean-sys">

# empyrean-sys
Low-level FFI bindings to the libempyrean astrodynamics C ABI

<a href="https://github.com/Empyrean-Dynamics/empyrean/actions/workflows/ci.yml"><img src="https://github.com/Empyrean-Dynamics/empyrean/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://crates.io/crates/empyrean-sys"><img src="https://img.shields.io/crates/v/empyrean-sys.svg?style=flat-square&label=crates.io" alt="crates.io"></a>
<a href="https://docs.rs/empyrean-sys"><img src="https://img.shields.io/docsrs/empyrean-sys?style=flat-square&label=docs.rs" alt="docs.rs"></a>
<br>
<a href="Cargo.toml"><img src="https://img.shields.io/badge/rustc-1.90%2B-orange?style=flat-square&logo=rust" alt="MSRV 1.90"></a>
<a href="https://github.com/Empyrean-Dynamics/empyrean/blob/main/LICENSE-BSD"><img src="https://img.shields.io/badge/source-BSD--3--Clause-blue.svg?style=flat-square" alt="Source license"></a>
<a href="https://github.com/Empyrean-Dynamics/empyrean/blob/main/LICENSE-BINARY"><img src="https://img.shields.io/badge/binary-proprietary-lightgrey.svg?style=flat-square" alt="Binary license"></a>
<a href="https://doi.org/10.5281/zenodo.21318471"><img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.21318471-blue?style=flat-square" alt="DOI"></a>
<br>
<a href="https://claude.ai"><img src="https://img.shields.io/badge/Built%20with-Claude%20Code-D97757?logo=anthropic&logoColor=white&style=flat-square" alt="Built with Claude Code"></a>
<a href="https://www.empyrean-dynamics.com"><img src="https://img.shields.io/badge/Website-empyrean--dynamics.com-1a1a2e?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48bGluZSB4MT0iMiIgeTE9IjEyIiB4Mj0iMjIiIHkyPSIxMiIvPjxwYXRoIGQ9Ik0xMiAyYTE1LjMgMTUuMyAwIDAgMSA0IDEwIDE1LjMgMTUuMyAwIDAgMS00IDEwIDE1LjMgMTUuMyAwIDAgMS00LTEwIDE1LjMgMTUuMyAwIDAgMSA0LTEweiIvPjwvc3ZnPg==&logoColor=white&style=flat-square" alt="Website"></a>
<a href="https://github.com/Empyrean-Dynamics"><img src="https://img.shields.io/badge/GitHub-Empyrean--Dynamics-1a1a2e?logo=github&logoColor=white&style=flat-square" alt="GitHub"></a>

---

empyrean-sys exposes the C ABI of `libempyrean` to Rust as raw,
`unsafe`, bindgen-generated declarations. It does not attempt to wrap,
type-check, or RAII-manage the underlying handles.

```toml
[dependencies]
empyrean-sys = "0.9.0"
```

```rust
use empyrean_sys::*;

// All entry points are unsafe; pointer ownership and lifetime are the
// caller's responsibility. See include/empyrean.h at the repository
// root for the authoritative C ABI documentation.
unsafe {
    // Null data_dir = the platform default data directory; downloads
    // any missing kernels. Returns null on error (see empyrean_last_error).
    let ctx: *mut EmpyreanContext = empyrean_context_from_data_dir(std::ptr::null());
    assert!(!ctx.is_null());
    empyrean_context_free(ctx);
}
```

**Most users want the safe wrapper instead** — see the
[`empyrean`](https://crates.io/crates/empyrean) crate, which builds on
empyrean-sys to provide typed handles, `Result`-returning entry points,
and Rust-native lifetime management.

## What the bindings cover

The declarations track the full C ABI at `EMPYREAN_ABI_VERSION`
(currently `2`), including the v0.9.0 wide-parameter fitting surface
and the ABI-2 output surface below. ABI 2 grew existing struct shapes
by appending fields, so consumers must recompile against the matching
header. Each type below maps 1:1 onto a C struct; consult `include/empyrean.h`
at the repository root for field-level semantics.

- **Wide-parameter OD.** `empyrean_determine` / `empyrean_refine` solve
  beyond the 6-parameter state and the Marsden A1/A2/A3 non-gravitational
  block for the cometary outgassing time delay DT, the SRP area-to-mass
  ratio AMRAT, and per-segment thrust Δv corrections on continuous-thrust
  arcs — each partial produced analytically by the hyperdual integrator. The per-axis `EmpyreanSolveFor`
  (`marsden` / `dt` / `amrat` / `thrust_segments`) is read under
  `EMPYREAN_SOLVE_FOR_EXPLICIT`. DT / AMRAT / thrust are refine-path solves:
  the input orbit must carry the corresponding prior (its declared variance)
  to open the axis, and requesting an axis without its prior errors out
  rather than returning a zeroed or defaulted column.
- **Tagged solved covariance.** `EmpyreanSolvedCovariance` carries the
  fitted-parameter identities alongside the matrix: `marsden_slot`,
  `dt_slot`, `amrat_slot`, and `thrust_slots` locate each parameter's
  row/column, with `EMPYREAN_SLOT_NONE` marking an axis that was not solved.
  Read a parameter's variance by its slot — the `width` alone is ambiguous.
- **Post-OD photometry.** With `EmpyreanODConfig::has_photometry` set,
  `EmpyreanPhotometryConfig` requests a fit recovering absolute magnitude
  `H` and the phase-function slopes from the observation magnitudes,
  climbing the H-only → HG12 → HG1G2 ladder (Muinonen et al. 2010) to the
  richest model the arc's phase coverage supports. `EmpyreanODPhotometryResult`
  reports the fitted `h` / `slope1` / `slope2`, the `model_used`, a 3×3
  `covariance` giving an honest 1σ on `h`, plus
  `n_mags_dropped_unconvertible` and the distinct offending band codes
  in `dropped_bands` when magnitudes could not be converted to V — the
  observations' astrometry is unaffected.
- **Ephemeris covariances and warnings.** Each `EmpyreanEphemerisEntry`
  carries a 6×6 sky-plane covariance over (rho, RA, Dec) and their rates
  in (AU, deg) units, and the aberrated (light-time corrected)
  barycentric ICRF Cartesian state at the photon-emission epoch with its
  own 6×6 covariance — `has_covariance` / `has_aberrated_covariance`
  gate each block (absent unless the input orbit carried a state
  covariance). `EmpyreanEphemerisResult` returns run-level non-fatal
  `warnings`, e.g. Earth-orientation kernel coverage gaps served by the
  analytic IAU 2006 fallback, or rows whose sensitivity chain was
  skipped.
- **Per-observation diagnostics.** `EmpyreanObservationResult` carries
  radar delay/Doppler residuals (observed − predicted, seconds / hertz,
  with `radar_chi2`, `radar_dof`, `radar_probability`, and the combined
  `radar_variance`), the D-optimality `influence_information_loss` on
  removal (+∞ marks an indispensable observation), and
  `along_cross_covariance_arcsec2` completing the 2×2 along/cross-track
  covariance.
- **Covariance trust verdict.** `EmpyreanODResult::covariance_trust`
  reports an event-aware verdict on the delivered covariance
  (`EMPYREAN_COVARIANCE_TRUST_*`): `TRUSTED`, `ENCOUNTER_INTERVENES`
  (with the intervening close-approach or high-nonlinearity event and
  whether a second-order state-only correction can recover it), or
  `WEAKLY_DETERMINED_HIGH_N`. `NOT_EVALUATED` means no gate ran —
  absence of a verdict is not trust.
- **Impact-probability detail.** Each `EmpyreanImpactProbability` row
  carries the geodetic impact point (latitude / longitude / altitude on
  the body's reference ellipsoid, when the surface projection is
  available), the Monte-Carlo 95% binomial confidence half-width on
  `ip_mc`, the second-order corrected mean miss distance with its 1σ
  uncertainty and skewness, the closest-approach distance `gradient`
  (6-vector) and `distance_hessian` (6×6) with respect to the initial
  state, and the adaptive Gaussian-mixture component count.
- **Plan evaluation.** Radar candidates in `EmpyreanPlanCandidate`
  carry the effective SNR (linear power ratio), one-way range (km),
  link-budget provenance notes, and the measurement mode;
  `EmpyreanPlanResult` carries the predicted optical ephemeris (epoch
  MJD TDB, RA, Dec per optical candidate).
- **Basis-tagged mixture components.** Each `EmpyreanMixtureComponent`
  is tagged with the reference `frame` and center-body `origin` (NAIF
  id) its mean and covariance are expressed in.

## Runtime requirement

empyrean-sys opens `libempyrean.{dylib,so}` at run time via
`libloading` (dlopen). The library is distributed separately as a
binary release on
[GitHub](https://github.com/Empyrean-Dynamics/empyrean/releases) and
inside the published Python wheel. The path is resolved from the
`EMPYREAN_LIB` environment variable if set, else a `libempyrean.*`
sitting next to the loaded module, else a build-time location — an
`EMPYREAN_LIB_DIR` override, a sibling `../target/release` build, or
a checksum-pinned download from the GitHub release (in that order).
The FFI bindings are pre-generated and committed, so no C header,
libclang, or bindgen is needed to build.

Prebuilt engine binaries are currently published for four targets:
macOS arm64 (`macos-aarch64`), macOS x86_64 (`macos-x86_64`), Linux
x86_64 (`linux-x86_64`), and Linux aarch64 (`linux-aarch64`). On other
targets the build stops with an error unless `EMPYREAN_LIB_DIR` points
at an engine build.

## License

Source code in this crate is licensed under the
[BSD 3-Clause License](LICENSE). The closed-source `libempyrean`
runtime it loads at runtime is governed by a separate proprietary binary
license; see the main repository for the dual-license breakdown.

Copyright © 2024–2026 Joachim Moeyens. All rights reserved.