stem-branch 0.3.0

Native Rust port of stem-branch's solar ephemeris core (full VSOP87D Earth series + JPL DE441-fitted correction + IAU2000B nutation).
Documentation

stem-branch (Rust)

Crates.io Docs.rs License: Apache-2.0 Rust CI

Native Rust port of stem-branch's astronomical core. Computes the Sun's geocentric ecliptic state (full VSOP87D Earth series + JPL DE441-fitted correction + IAU2000B nutation), the Moon's apparent position (full ELP/MPP02 theory), and the complete Chinese lunisolar calendar (new moons, solar terms, and 閏月 leap months) — no runtime dependencies, no JavaScript.

use stem_branch::solar_ecliptic_state;

// Julian Ephemeris Day in Terrestrial Time (J2000.0).
let s = solar_ecliptic_state(2451545.0);
assert!((s.apparent_longitude_degrees - 280.368152).abs() < 1.0 / 3600.0);
// s.true_longitude_degrees   — mean equinox of date
// s.apparent_longitude_degrees — + IAU2000B nutation + aberration
// s.radius_au                 — Sun–Earth distance

The Chinese lunisolar calendar — Lunar New Year, leap months (閏月), conversion:

use stem_branch::{gregorian_to_lunisolar, lunar_new_year, CivilDate};

// 2023-03-22 is 閏二月初一 — day 1 of the leap 2nd month of 2023.
let d = gregorian_to_lunisolar(CivilDate { year: 2023, month: 3, day: 22 });
assert_eq!((d.year, d.month, d.day, d.is_leap_month), (2023, 2, 1, true));

// Lunar New Year (正月初一) 2024 falls on 2024-02-10 (Beijing).
let lny = lunar_new_year(2024);
assert_eq!((lny.year, lny.month, lny.day), (2024, 2, 10));

The Moon's apparent geocentric position (ELP/MPP02):

use stem_branch::moon_position;

// Julian Ephemeris Day (TT). At a new moon the Moon's longitude meets the Sun's.
let m = moon_position(2451545.0);
// m.longitude_degrees / m.latitude_degrees — apparent ecliptic, of date
// m.distance_km                            — geocentric distance
// m.ra_degrees / m.dec_degrees             — apparent equatorial
assert!((356_000.0..=407_000.0).contains(&m.distance_km));

Input is a Julian Ephemeris Day in Terrestrial Time; the UTC↔TT / ΔT boundary is the caller's responsibility.

Validation

The implementation is validated against JPL Horizons (DE441 ephemeris) — an independent third-party oracle, the same reference the upstream TypeScript stem-branch is validated against. The test asserts solar_ecliptic_state().apparent_longitude_degrees against JPL's apparent geocentric ecliptic longitude of the Sun. The expected values are authored by JPL, not by us, so the test is an independent check rather than a circular one.

Result — apparent ecliptic longitude vs JPL DE441 (1900–2100 CE):

Instant (TT) JD_TT this crate (°) JPL DE441 (°) |Δ|
1900-01-01 00:00 2415020.5 280.1533022 280.1533171 0.054″
1940-06-15 06:00 2429795.75 83.9726193 83.9726417 0.081″
1980-09-23 00:00 2444505.5 180.1158230 180.1158349 0.043″
2000-01-01 12:00 (J2000) 2451545.0 280.3681559 280.3681519 0.014″
2024-03-20 03:00 2460389.625 359.9947954 359.9947742 0.076″
2060-12-21 00:00 2473814.5 269.8705294 269.8705076 0.079″
2100-07-04 12:00 2488254.0 102.6537711 102.6537510 0.073″

Maximum residual: 0.081″ — identical to the upstream TypeScript engine (the Rust port reproduces the same VSOP87D + DE441 model bit-for-bit). For reference, the upstream engine's headline figure is mean 1.05 s solar-term timing vs JPL DE441 over 209–2493 CE; see the full accuracy report.

Reference-value provenance (source, identity, license) is documented in tests/data/README.md. JPL Horizons output is a U.S. Government work in the public domain.

Reproduce

cargo test            # runs the JPL DE441 oracle tests
cargo clippy --all-targets -- -D warnings
cargo fmt --check

How it's built

The 2,077 VSOP87D Earth terms and 77 IAU2000B nutation rows are generated from the canonical TypeScript source by scripts/gen-rust-solar-data.mjs, so the Rust crate and the TypeScript engine share a single source of truth and cannot silently drift. Only the math (≈130 lines in src/lib.rs) is hand-written; the coefficient tables are mechanical translations.

The port matches the TypeScript implementation faithfully, including JavaScript's truncated-remainder (%) semantics for argument reduction and the literal 206264.806 arcsec-per-radian factor used by the DE441 correction polynomial.

Scope

Covers the solar ephemeris (solar_ecliptic_state), the Moon ephemeris (moon_position, ELP/MPP02), and the full Chinese lunisolar calendar (gregorian_to_lunisolar, lunar_months_for_year, lunar_new_year, plus new_moon_jde and find_solar_term_moment). The planets share the same VSOP series machinery upstream and can be ported the same way when needed.