Skip to content

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog. Brahe's versioning and deprecation policy is modeled on NumPy's policy rather than strict SemVer — see the versioning documentation for the full policy, including the transitional deprecation window currently in effect.

Each release groups entries under the Keep a Changelog section headings in the order Added, Changed, Deprecated, Removed, Fixed. Entries in Deprecated correspond to APIs that still work in this release but emit a DeprecationWarning or FutureWarning; entries in Removed list APIs that previous releases had deprecated.

[1.8.0] - 2026-10-16

Added

  • Crate-private utils::batch primitives (batch_map, batch_zip, batch_map_epochs, PARALLEL_THRESHOLD) with the length-1-or-N broadcast rule and shared-epoch context hoisting used by all vectorized transformation functions. @duncaneddy (#436)
  • Batch Earth frame transformations rotations_/positions_/states_{eci_to_ecef,ecef_to_eci,gcrf_to_itrf,itrf_to_gcrf} and positions_/states_{gcrf_to_eme2000,eme2000_to_gcrf} in Rust; a shared epoch hoists the IAU 2006/2000A rotation matrices across the batch. @duncaneddy (#437)
  • Python rotation_/position_/state_* Earth frame functions accept batches of vectors with an axis keyword (default -1, numpy component-axis convention) and Epoch | Sequence[Epoch] epoch arguments; single-vector calls are unchanged. @duncaneddy (#437)
  • Learn page "Vectorized Transformations" and examples/frames/vectorized_transforms.{py,rs}. @duncaneddy (#437)
  • Batch lunar, Mars, EMB, IAU, synodic (EMR/SER/GSE), and generic frame-router transformations in Rust (rotations_/positions_/states_*, rotations_icrf_to_body_fixed_iau, rotations_/positions_/states_frame_to_frame). @duncaneddy (#438)
  • Python planetary, synodic, and router frame functions accept batches (axis keyword) and epoch sequences. @duncaneddy (#438)
  • Batch coordinate transformations in Rust: states_koe_to_eci/states_eci_to_koe (+ _for_body), positions_{geodetic,geocentric}_to_ecef and inverses, positions_/states_{radec_to_inertial,inertial_to_radec}, positions_{radec_to_azel,azel_to_radec}, rotations_ellipsoid_to_{enz,sez} and inverses, relative_positions_{ecef_to_enz,enz_to_ecef,ecef_to_sez,sez_to_ecef}, positions_{enz,sez}_to_azel, points_in_polygon. @duncaneddy (#439)
  • Python coordinate functions accept batches with the axis keyword; relative-position functions broadcast a single site across a batch of positions (and vice versa); position_radec_to_azel/position_azel_to_radec accept epoch sequences. @duncaneddy (#439)
  • Batch relative-motion transformations in Rust (rotations_/states_* for RTN, ROE, and Keplerian-pair conversions) with chief/deputy broadcasting. @duncaneddy (#440)
  • Python relative-motion functions accept batches with the axis keyword; a single chief or deputy broadcasts across the other argument's batch. @duncaneddy (#440)
  • Batch equinoctial conversions states_koe_to_equinoctial / states_equinoctial_to_koe in Rust and batched input in Python. @duncaneddy (#441)
  • Python Keplerian property and anomaly functions accept (n, 6) element-set batches and element-wise arrays with numpy broadcasting against e; semimajor_axis* functions accept arrays; orbital_period_from_state accepts batched states. @duncaneddy (#441)
  • set_vectorization_length_threshold(n) / get_vectorization_length_threshold() in Rust (brahe::utils) and Python to configure the batch length at which vectorized functions use the global thread pool (default 1024). @duncaneddy (#451)
  • Typed CCSDS ADM reference-frame vocabularies mirroring the SANA registries (CCSDSCelestialBodyFrame, CCSDSOrbitRelativeFrame, CCSDSSpacecraftBodyFrame, ADMReferenceFrame), bidirectional conversion bridges between those vocabularies and the native ReferenceFrame type, and the APMHeader/AEMHeader/TDMHeader message headers, as the foundations for CCSDS APM/AEM/TDM support. @duncaneddy (#453)
  • BRAHE_NETWORK_MODE environment variable (online, offline, offline-strict) gating every network request brahe makes and controlling whether cached files past their time-to-live are refreshed, served, or rejected; network_mode() in Rust and brahe.network_mode() in Python read the active mode. @duncaneddy (#454)
  • docs/learn/utilities/environment_variables.md reference page for every environment variable the library reads. @duncaneddy (#454)
  • Attitude kinematics functions quaternion_derivative, angular_velocity_from_quaternion_derivative, euler_rates_to_angular_velocity, and angular_velocity_to_euler_rates, grounded in Diebel (2006) and verified by finite differences against the existing attitude representations. The quaternion pair takes a scalar_first argument selecting the component layout of the raw quaternion derivative, matching Quaternion::from_vector and Quaternion::to_vector. @duncaneddy (#455)
  • scripts/warm_data_cache.py --only <family> / --refresh and the celestrak:group:<name> manifest form. @duncaneddy (#460)
  • CCSDS APM (Attitude Parameter Message, 504.0-B-2) support: typed message structs with quaternion, Euler-angle, angular-velocity, spin, inertia, and maneuver blocks; KVN, XML, and JSON reading and writing; and Python bindings. The APM XML writer rejects characters XML 1.0 forbids, matching the other XML writers. @duncaneddy (#464)
  • spd_sqrtm_dmatrix for the square root of a dynamically sized symmetric positive semi-definite matrix, computed by eigendecomposition. It symmetrizes its input, clamps eigenvalues that are negative only to within round-off rather than rejecting them, and reports a decomposition that fails to converge instead of returning NaN. @duncaneddy (#467)
  • CCSDS AEM (Attitude Ephemeris Message, 504.0-B-2) support: segmented attitude ephemerides with all nine attitude data types; KVN, XML, and JSON reading and writing; conversion to and from attitude trajectories; and Python bindings. @duncaneddy. @duncaneddy (#471)
  • AttitudeTrajectory, relating two ReferenceFrame endpoints, with slerp, linear, and Lagrange attitude interpolation and attitude kinematics-based normalization of AEM data on ingest. It implements OrientationProvider, so an attitude history can serve as a frame-graph orientation source. @duncaneddy. @duncaneddy (#471)
  • AEM::register_for(name) registers a single-segment AEM's attitude as the orientation of an object's body frame, binding the message's unbound body endpoint to the object and inverting the series when the celestial endpoint is REF_FRAME_B. @duncaneddy. @duncaneddy (#471)
  • The SPICE kernels the unit test suites load are included in the repository, so cargo test succeeds immediately after cloning without a separate data-download step. @duncaneddy (#496)
  • CelestialFrame::MOD and CelestialFrame::TOD in the reference frame router, including from_string/Display support and the corresponding CelestialFrame.MOD/CelestialFrame.TOD Python attributes. @duncaneddy (#526)
  • Equinox-based Earth frame transformations bias_precession_iau2000, nutation_iau2000b, gast_rotation_iau2000b, and the rotation_/position_/state_ functions with batch forms for GCRF/MOD, MOD/TOD, GCRF/TOD, and TOD/ITRF in both directions (Rust and Python). @duncaneddy (#526)
  • MOD and TOD frame choices in the brahe transform CLI commands. @duncaneddy (#526)
  • Learn page and API reference for the equinox-based frames with six standalone examples in Python and Rust. @duncaneddy (#526)
  • OrbitTrajectory.to_frame(frame) (Rust DOrbitTrajectory::to_frame / SOrbitTrajectory::to_frame) converting a trajectory to any ReferenceFrame through the reference frame router, including orbit-relative frames of registered objects. @duncaneddy (#527)
  • ReferenceFrame == CelestialFrame comparisons in Rust (PartialEq both directions) and Python (ReferenceFrame.__eq__ accepts a CelestialFrame). @duncaneddy (#527)
  • KeplerianPropagator and SGPPropagator accept any Earth-rooted ReferenceFrame as output frame (for example CelestialFrame.TOD or a bound RTN frame), and Keplerian output is accepted in EME2000 as well as GCRF. @duncaneddy (#527)
  • OPM.state_in_frame(frame) (Rust OPM::state_in_frame) converting the message state vector from its declared REF_FRAME to any CelestialFrame or ReferenceFrame through the reference frame router. @duncaneddy (#528)
  • TryFrom<&CCSDSRefFrame> for CelestialFrame (with TryFrom<&CCSDSRefFrame> for ReferenceFrame delegating to it) and TryFrom<&ReferenceFrame> for CCSDSRefFrame mapping EME2000/J2000, GCRF, the ITRF realizations, and TOD (the reverse direction writes ITRF as ITRF2000 and rejects MOD, which has no ODM token). @duncaneddy (#528)
  • ADM TOD_EARTH and MOD_EARTH reference-frame tokens map to CelestialFrame::TOD and CelestialFrame::MOD in both directions. @duncaneddy (#528)
  • PrecessionNutationModel (IAU2006A, IAU2000B) with set_precession_nutation_model / get_precession_nutation_model and bias_precession_nutation_model(epc, model) in Rust and Python. @duncaneddy (#532)
  • FrameAxes and FrameCenter types, CelestialFrame::Centered, CelestialFrame::centered(center, axes), CelestialFrame::axes(), and CelestialFrame::center() in Rust and Python (FrameAxes, FrameCenter, CelestialFrame.Centered(center, axes), .axes, .center, .center_naif_id). @duncaneddy (#536)
  • NAIFId::name(), NAIFId::from_name() (accepting NAIF aliases such as SSB and EMB), and Display for NAIFId; Python NAIFId.from_name() and NAIFId.naif_name. @duncaneddy (#536)
  • Python ReferenceFrame.celestial_frame returning the wrapped CelestialFrame (or None), so a trajectory's frame exposes axes and center. @duncaneddy (#536)
  • brahe::ccsds::interop::segment_celestial_frame, state_for_segment, and state_into_segment_axes for writing trajectories into OEM segments, shared by the Python bindings. @duncaneddy (#536)
  • CCSDSRefFrame::ICRF and CCSDSRefFrame::MCI tokens; TryFrom<&CCSDSRefFrame> for FrameAxes; brahe::ccsds::interop::odm_celestial_frame resolving a message's REF_FRAME, REF_FRAME_EPOCH, and center to a CelestialFrame; NAIFId implements FromStr. @duncaneddy (#536)
  • Example examples/frames/mars_centered_eme2000 and the "Axes and Centers" section of the frame transformations page. @duncaneddy (#536)
  • Keplerian element trajectories and propagator output in the MOD and TOD frames for OrbitTrajectory, KeplerianPropagator, and SGPPropagator, alongside the ICRF-aligned inertial frames and EME2000. @duncaneddy (#539)
  • FrameAxes::TEME and CelestialFrame::TEME in the reference frame router with from_string/Display support, the FrameAxes.TEME and CelestialFrame.TEME Python attributes, and the TEME choice in the brahe transform CLI commands. @duncaneddy (#542)
  • Keplerian element trajectories and propagator output in TEME axes for OrbitTrajectory, KeplerianPropagator, and SGPPropagator, alongside MOD and TOD. @duncaneddy (#542)
  • gmst82, greenwich_mean_sidereal_rotation, and the rotation_/position_/state_ functions with batch forms for GCRF/TEME and TEME/ITRF in both directions (Rust and Python). @duncaneddy (#542)
  • CCSDS mappings for the ODM TEME and ADM TEMEOFDATE reference frame tokens to CelestialFrame::TEME in both directions; OEM, OPM, and AEM messages in TEME convert to native trajectories and states. @duncaneddy (#542)
  • Learn page and API reference for the TEME frame with three standalone examples in Python and Rust, and a TEME output section in the SGP propagation guide. @duncaneddy (#542)
  • FrameAxes::TODofEpoch(Epoch) and FrameAxes::TEMEofEpoch(Epoch) epoch-frozen inertial axes, frame_epoch on FrameAxes and CelestialFrame, and the CelestialFrame::tod_of_epoch / teme_of_epoch constructors, mirrored in Python as FrameAxes.TODofEpoch, FrameAxes.TEMEofEpoch, CelestialFrame.tod_of_epoch, CelestialFrame.teme_of_epoch, and the frame_epoch properties. @duncaneddy (#545)
  • Keplerian element trajectories and propagator output in the of-epoch axes, alongside MOD, TOD, and TEME. @duncaneddy (#545)
  • ref_frame_epoch keyword parameter and property on the Python OEMSegment, in owned and OEM-attached modes. @duncaneddy (#545)
  • brahe.clients Python namespace re-exporting celestrak, spacetrack, datasets.gcat and RateLimitConfig, mirroring brahe::clients; the flat module paths are unchanged. @duncaneddy (#548)
  • brahe::math::is_symmetric for square-matrix symmetry checks within a relative tolerance. @duncaneddy (#549)
  • brahe::itc (brahe.itc): ITC, ITCHeader, ITCStateVector, ITCCovarianceFrame with from_str, from_file, to_string, to_file, record builders, and file_name for compliant Space-Track names. @duncaneddy (#549)
  • brahe::spacetrack::SpaceTrackEphemerisFileName and SpaceTrackEphemerisFileCategory (brahe.spacetrack) to parse and generate the Spaceflight Safety Handbook file-name convention. @duncaneddy (#549)
  • Starlink Modified ITC test assets under test_assets/starlink/. @duncaneddy (#549)
  • ITC::to_trajectory and ITC::to_trajectory_with_covariance_variant (ITC.to_trajectory(covariance_variant=None) in Python) producing a six-dimensional Cartesian OrbitTrajectory with covariance rotated from RTN into the state frame. @duncaneddy (#551)
  • ITC::from_trajectory and ITC::from_trajectory_with_covariance_variant building a message from a trajectory's samples, converting frames and carrying covariance through the GCRF/EME2000 bias and into RTN. @duncaneddy (#551)
  • TryFrom<&ITC> for DOrbitTrajectory. @duncaneddy (#551)
  • ITCStateVector::to_vector and From<&ITCStateVector> for SVector6 (ITCStateVector.to_vector() in Python). @duncaneddy (#551)
  • brahe::utils::dmatrix_from_smatrix and smatrix_from_dmatrix, shared fixed-to-dynamic matrix conversions. @duncaneddy (#551)
  • StarlinkClient (brahe::starlink, bh.StarlinkClient) with get_manifest, refresh_manifest, cached_manifest, previous_manifest, download_ephemeris, get_ephemeris, get_trajectory, get_trajectory_with_covariance_variant, save_ephemeris, download_all, save_all, prune_cache, and cached_files. @duncaneddy (#554)
  • StarlinkManifest and StarlinkManifestEntry with lookups by NORAD ID and name, changed_since, and to_dataframe. @duncaneddy (#554)
  • get_starlink_cache_dir and the brahe.clients.starlink namespace. @duncaneddy (#554)
  • time::conversions::days_in_month (bh.days_in_month), shared by the epoch calendar arithmetic and the HTTP-date parser. @duncaneddy (#554)
  • utils::fs::move_file, copy_file, is_within, and validate_path_component, shared file-management helpers used by the Starlink client and the Space-Track file-name parser; save_ephemeris and save_all reject any destination inside the cache tree before downloading. @duncaneddy (#554)
  • brahe starlink manifest and brahe starlink download CLI commands; download takes --output to place files outside the cache and --keep-cached to copy rather than move them. @duncaneddy (#555)
  • get_starlink_cache_dir in Python. @duncaneddy (#555)
  • scripts/seed_starlink_cache.py as just seed-starlink-cache, run by the offline CI example and docs jobs; it refuses to replace a live manifest without --force. @duncaneddy (#555)
  • Examples itc/itc_parse, itc/itc_to_trajectory, itc/itc_write, datasets/starlink_get_ephemeris, datasets/starlink_manifest, and datasets/starlink_trajectory_access. @duncaneddy (#555)
  • Learn pages for Starlink public ephemerides and the Modified ITC format; Library API pages for StarlinkClient, StarlinkManifest, ITC and SpaceTrackEphemerisFileName; updates to the ephemeris, caching, rate limiting, file operations and data sources pages. @duncaneddy (#555)
  • brahe::frames::state_transform_jacobian, rotate_covariance, rotate_covariance_6, covariance_frame_to_frame, and validate_covariance_shape, plus the batch forms state_transform_jacobians, rotate_covariances, and covariances_frame_to_frame; the Python bh.state_transform_jacobian, bh.rotate_covariance, and bh.covariance_frame_to_frame dispatch on input shape like state_frame_to_frame. @duncaneddy (#557)
  • brahe::relative_motion::jacobian_rtn_to_eci, jacobian_eci_to_rtn, covariance_rtn_to_eci, and covariance_eci_to_rtn with an OrbitRelativeFrameVariant argument (Python mirrors). @duncaneddy (#557)
  • OrbitTrajectory.covariance_in_frame(frame, epoch), covariance_itrf, covariance_ecef, covariance_eme2000, and covariance_rtn_with_variant(epoch, variant) on both trajectory types. @duncaneddy (#557)
  • brahe::math::skew_symmetric, block_diagonal, symmetrize, symmetrize_6, and is_symmetric. @duncaneddy (#557)

Changed

  • scripts/warm_data_cache.py now warms each manifest entry independently with retries, aggregates failures into one report, and rejects an unrecognized manifest prefix with a named error rather than passing it to load_spice_kernel. @duncaneddy (#424)
  • Python Keplerian property and anomaly functions no longer read a 1-D 6-element array as one element set: 1-D arrays of any length are element-wise values (orbital_period(oe) returns six periods; use orbital_period(oe[0]) or a (1, 6) array). Element sets are passed as (n, 6) rows. @duncaneddy (#441)
  • batch_state_koe_osc_to_mean / batch_state_koe_mean_to_osc renamed to states_koe_osc_to_mean / states_koe_mean_to_osc in Rust and Python. @duncaneddy (#441)
  • scripts/add_stub_annotations.py renders A or B docstring return types as Union[A, B] in the generated stubs. @duncaneddy (#441)
  • Development tooling: ruff pre-commit hook updated to v0.16.3 with the previous lint rule set and Python-only file discovery pinned in pyproject.toml. @duncaneddy (#443)
  • Development tooling: the repository lints under ruff 0.16 default rules; Typer parameter calls are declared as immutable defaults. @duncaneddy (#444)
  • Remove pin of time crate after upstream fixes in cookies crate landed. @duncaneddy (#446)
  • Updated uv.lock to address security warnings. @duncaneddy (#449)
  • Pin minimum supported rust version (MSRV) to 1.95 for build consistency. @duncaneddy (#449)
  • Celestrak single-object lookups (catnr, intdes, name) resolve from a cached copy of the active group, fetching it once instead of one request per object; objects not in active are still requested individually, and a name search that matches in active returns only active objects. @duncaneddy (#457)
  • CI test, example, plot, and docs steps all run with BRAHE_NETWORK_MODE=offline from per-family restored caches after an online warm step; loopback URLs are exempt from BRAHE_NETWORK_MODE, which is what lets the unit-test jobs' local mock servers keep working offline. The weekly warm_data_cache.yml warms each family independently, refreshes a run-id-keyed Celestrak cache (active, starlink, gps-ops, and four debris groups) so no PR run contacts Celestrak, and saves the plot-resource caches independently of each other. @duncaneddy (#460)
  • The Celestrak-backed documentation examples run on every PR and docs build from the warmed Celestrak cache, so their output and figures are always published; the NETWORK flag now marks only examples whose data cannot be served from the CI caches. @duncaneddy (#462)
  • just setup warms the example and plot data caches via download-resources, so the example and plot recipes run from a warm cache instead of racing cold downloads. @duncaneddy (#462)
  • The KVN, XML, and JSON codecs are split into one reader/writer module per message type, with the token stream, intermediate structs, and shared block helpers in a common module per codec; public paths and wire output are unchanged. @duncaneddy (#464)
  • Default IntegratorConfig tolerances tightened from abs_tol 1e-6 / rel_tol 1e-3 to 1e-8 / 1e-10, since the previous values permit kilometre-scale per-day error once adaptive stepping is effective. @duncaneddy (#469)
  • NumericalPropagationConfig::high_precision() tightened to 1e-12 absolute and relative; its previous relative tolerance of 1e-8 was looser than the new default. @duncaneddy (#469)
  • Default IntegratorConfig::max_step_attempts raised from 10 to 25. Each rejection shrinks the step by at most min_step_scale_factor, so ten attempts bound one call to roughly seven orders of magnitude of reduction, which the tightened tolerances no longer leave comfortable through a low perigee pass. @duncaneddy (#469)
  • AttitudeTrajectory accepts repeated epochs, which represent a discontinuity so an impulsive attitude maneuver can hold its pre- and post-maneuver state at the same instant. A query at a repeated epoch returns the most recently added state, and Lagrange windowing is confined to the run of strictly increasing epochs containing the bracket rather than fitting a polynomial across the jump. This matches the orbit and generic trajectories. @duncaneddy. @duncaneddy (#471)
  • state_providers is now a module directory with one file per provider family; every trait is re-exported, so existing paths resolve unchanged. @duncaneddy. @duncaneddy (#471)
  • Celestrak GP groups used by examples and documentation plots are committed under test_assets/celestrak and seeded into the local cache, so example and plot runs no longer contact Celestrak. Live client behavior remains covered by the integration test suite. @duncaneddy (#485)
  • Rust documentation tests run with the default thread count rather than being forced onto a single thread, which reduces their runtime by roughly a factor of three. @duncaneddy (#492)
  • Rust coverage instrumentation runs only on the platform whose coverage is actually uploaded. The other platforms run the same tests without instrumentation, which is substantially faster. @duncaneddy (#494)
  • The Rust and Python unit test workflows no longer restore, warm, or transfer a NAIF kernel cache, since the kernels they need are committed. @duncaneddy (#496)
  • The Python test suite runs in parallel across available cores, distributed per module so the suite's module-scoped fixtures and ordering constraints continue to hold. @duncaneddy (#501)
  • Breaking: ReferenceFrame renamed to CelestialFrame in Rust and Python — the frame router, all frame-router functions, and every associated binding now use the new name. The ReferenceFrame name now denotes the new unified supertype introduced in #508 spanning celestial, orbit-relative, and body frames, so code using ReferenceFrame::GCRF becomes CelestialFrame::GCRF. @duncaneddy (#507)
  • Compile the Epoch string-parsing regexes once instead of on every Epoch::from_string call, making string-to-epoch construction roughly three orders of magnitude faster. @duncaneddy (#519)
  • (Chore) Consolidate test_data/ into test_assets/ to reduce folder duplication. @duncaneddy (#521)
  • Clarified misinterpretation of TOD-based OPM file as ECEF in OPM example until full transformation can be implemented. @duncaneddy (#521)
  • Trajectory, propagator, and CCSDS interop frame fields and arguments are ReferenceFrame; constructors accept a CelestialFrame or ReferenceFrame (bh.CelestialFrame.ECI where bh.OrbitFrame.ECI was used; bh.CelestialFrame.LCI for the former BodyCenteredInertial(301)). @duncaneddy (#527)
  • OrbitTrajectory.frame returns a ReferenceFrame whose string form is the celestial name (GCRF, ITRF, LCI) rather than the former ECI/ECEF/BCI(301) labels. @duncaneddy (#527)
  • Keplerian element trajectories and propagator outputs are valid in ICRF-aligned inertial celestial frames and EME2000; CelestialFrame.GCRF is accepted wherever ECI was (they are the same frame). @duncaneddy (#527)
  • plot_trajectory_3d converts trajectories around any central body into that body's inertial frame with to_frame instead of rejecting a mismatched frame. @duncaneddy (#527)
  • OEM.register_for accepts any celestial trajectory frame; the Python OEM-from-trajectory path converts through the router, so a non-Earth body-centered trajectory is re-centered instead of rejected. @duncaneddy (#527)
  • to_keplerian keeps the trajectory's own frame for element input and, for Cartesian input, produces elements about the frame's center (accepted for ICRF-aligned inertial frames and EME2000; rejected for Earth-fixed, of-date, orbit-relative, and body frames, where it previously converted through GCRF and labeled the result ECI). state_koe_osc short-circuits exactly for Keplerian trajectories in any ICRF-aligned frame. @duncaneddy (#527)
  • covariance_eci/covariance_gcrf error, naming the frame, for trajectory frames without a covariance representation; propagator error messages for the Keplerian-frame and Earth-only rules changed. @duncaneddy (#527)
  • OrbitalTrajectory gains a required to_frame method (breaking for external implementors). Propagator constructors, with_output_format, and stepping can return router errors for frames that cannot be evaluated; KeplerianPropagator::reset panics if the output frame can no longer be resolved; set_initial_conditions leaves the propagator unchanged when a conversion fails. @duncaneddy (#527)
  • Python KeplerianPropagator(...) and set_initial_conditions accept angle_format=None for Cartesian input, mirroring the Rust Option<AngleFormat>; passing None with a Keplerian representation now raises BraheError instead of TypeError. @duncaneddy (#527)
  • ccsds_ref_frame_to_orbit_frame is renamed ccsds_ref_frame_to_reference_frame and returns a ReferenceFrame; the token mapping itself lives in the new ccsds_ref_frame_to_celestial_frame, which OEM::register_for uses directly. @duncaneddy (#527)
  • OEM segments declared in TOD load as OrbitTrajectory values whose frame is CelestialFrame::TOD through segment_to_dorbit_trajectory, segment_to_sorbit_trajectory, TryFrom<&OEM>, OEM::register_for, and Python OEM.to_trajectories(), instead of returning an error. @duncaneddy (#528)
  • Python OEMSegment.add_trajectory writes trajectories into segments declared in TOD, rotating from the trajectory frame through the reference frame router. @duncaneddy (#528)
  • ODM reference-frame tokens without a native trajectory frame report distinct errors: TEME and TDR have no native frame, orbit-relative tokens (RTN, TNW, RSW) require an object anchor, and unknown tokens are reported as unrecognized. @duncaneddy (#528)
  • Python OEMSegment.add_trajectory raises BraheError for an unsupported segment frame in both standalone and OEM-attached segments. @duncaneddy (#528)
  • The opm_maneuver_propagation examples convert the TOD state with state_in_frame, schedule the sample message's maneuvers one hour after the state epoch preserving their spacing, rotate EME2000 delta-V by the frame bias, and rotate RTN delta-V with the propagated state at ignition; the opm_init_propagator examples use state_in_frame for the ITRF sample; docs/learn/ccsds/opm.md and docs/learn/ccsds/oem.md describe TOD handling. @duncaneddy (#528)
  • GCRF/ITRF, ECI/ECEF, and GCRF/MOD/TOD transformations evaluate IAU 2006/2000A by default (previously IAU 2000B); results move by 0.33 mas RMS and up to 1.24 mas over 1990-2040 (0.25 m at GEO, 0.04 m at LEO), and the CIP evaluation is about eight times slower unless IAU2000B is selected. @duncaneddy (#532)
  • Equinox building blocks bias_precession_iau2000, nutation_iau2000b, and gast_rotation_iau2000b are replaced by bias_precession(epc, model), nutation(epc, model), and gast_rotation(epc, model). @duncaneddy (#532)
  • Documentation, rustdoc, and docstrings name the IAU 2006/2000A model and the switch; the router table rows for ITRF, MOD, and TOD are corrected. @duncaneddy (#532)
  • Frame modules share one implementation of state rotation and rotating-axes transport in frames::kinematics; no user-facing behavior changes. @duncaneddy (#534)
  • NAIFId::from(i32) returns the named variant for known IDs. @duncaneddy (#536)
  • OEM segments and OPM messages with a non-Earth CENTER_NAME load as Centered(axes, center) trajectories and states (for example Centered(MARS, EME2000) for OEMExample4.txt); OPM::state_in_frame accepts any center. @duncaneddy (#536)
  • keplerian_center accepts any celestial frame whose axes are ICRF, EME2000, MOD, or TOD about any center (extending #539 to centered frames); the trajectory covariance and Keplerian fast paths test axes rather than frame identity. @duncaneddy (#536)
  • icrf_aligned_inertial resolves through centered(ICRF, center). @duncaneddy (#536)
  • Writing a ReferenceFrame with ICRF axes to a CCSDS token gives GCRF for an Earth center, MCI for a Mars center, and ICRF otherwise. @duncaneddy (#536)
  • SGPPropagator converts its TEME output through the shared frame functions: GCRF and ITRF outputs use the pairwise transforms, state_eci shares the GCRF path, and every other frame goes through the router from TEME. GCRF, ITRF, and PEF values move by up to about 1e-2 m and 1e-5 m/s in low Earth orbit, which is the GMST 1982 evaluation gaining precision (two-part MJD instead of a single-double Julian date, about 1e-9 rad about the pole); the reference-value tests pin the recorded values at that level. @duncaneddy (#542)
  • SGPPropagator.state documents that it returns the raw SGP4 state in TEME; state_gcrf, state_itrf, and state_in_frame provide other frames. @duncaneddy (#542)
  • just set-version now also updates the version in CITATION.cff, and the release workflow validates that it matches the pushed tag. @duncaneddy (#544)
  • ODM TOD and TEME messages carrying REF_FRAME_EPOCH load as trajectories and states in the of-epoch frame about the message center instead of being converted to GCRF on load; odm_celestial_frame and segment_celestial_frame return only the CelestialFrame. @duncaneddy (#545)
  • CCSDSRefFrame::try_from writes the of-epoch axes as their parent TOD and TEME tokens; ADM conversion of TEMEOFEPOCH and of any epoch-frozen native frame returns an error naming the missing frame epoch. @duncaneddy (#545)
  • Group the Celestrak, Space-Track and GCAT clients under brahe::clients, re-exported at the crate root; brahe::datasets::gcat and the GCAT types and functions formerly exported from brahe::datasets are now brahe::gcat and crate-root exports (Python brahe.datasets.gcat is unchanged). @duncaneddy (#548)
  • Share the sliding-window rate limiter across clients as brahe::clients::RateLimitConfig; the module path brahe::spacetrack::rate_limiter is now brahe::clients::rate_limiter, while brahe::spacetrack::RateLimitConfig remains a re-export with unchanged defaults. @duncaneddy (#548)
  • Add --workspace argument to just lint and just lint-fix so that python bindings are linted as well. @duncaneddy (#553)
  • Celestrak and Starlink cache staleness share utils::cache::is_older_than, and mirror cache labels use utils::cache::short_hash (SHA-256). @duncaneddy (#554)
  • joss/paper.bib references now carry a doi field where one is registered on Crossref (11 entries). @goelakash (#556)
  • OrbitTrajectory::from_orbital_data and the covariance setters accept covariance in any Cartesian frame; to_frame, to_eci, to_gcrf, to_eme2000, to_itrf, and to_ecef rotate covariance for every frame pair, and a conversion to the same frame keeps it. @duncaneddy (#557)
  • covariance_rtn and covariance_rtn_with_variant on both trajectory types build the RTN frame about the trajectory's own central body from state_bci, so lunar and planetary trajectories get a body-centred RTN; Earth-orbit results are unchanged, and DNumericalOrbitPropagator::covariance_rtn is computed through the same shared Jacobian and symmetrized. @duncaneddy (#557)
  • frames::covariance is the public covariance module (brahe::frames::* exports it); math::covariance is private with its items re-exported through math::*. @duncaneddy (#557)
  • Keplerian-representation trajectories reject covariance rotation and covariance_rtn with a single error instead of silently applying a Cartesian Jacobian. @duncaneddy (#557)
  • Cargo build caches in CI are saved only by test-rust (one per OS) and test-examples (Linux); every other workflow job restores one of those read-only, and the wheel, sdist and release jobs run without a cargo cache. @duncaneddy (#568)
  • The rust-script example cache saved on main drops the per-example executables under deps/ and is rebuilt clean whenever Cargo.lock or the example build script changes. @duncaneddy (#568)
  • warm_data_cache.yml no longer runs the package validation builds. @duncaneddy (#568)
  • Update actions-rust-lang/setup-rust-toolchain from 1.17.0 to 2.0.0 across all workflows. @duncaneddy (#582)
  • Exclude .cargo from the published crate alongside .github. @duncaneddy (#582)
  • Update quick-xml to 0.42, uuid to 1.26.1, and lru to 0.18.5. @duncaneddy (#588)

Removed

  • scripts/warm_data_cache.py no longer warms Celestrak groups, and its --refresh flag is removed along with the Celestrak family it existed to clear. The celestrak family is removed from the data-cache action. @duncaneddy (#485)
  • OrbitFrame (Rust brahe::trajectories::OrbitFrame, Python brahe.OrbitFrame). @duncaneddy (#527)
  • ccsds_ref_frame_to_reference_frame and ccsds_ref_frame_to_celestial_frame; use ReferenceFrame::try_from(&ccsds_ref_frame) or CelestialFrame::try_from(&ccsds_ref_frame). @duncaneddy (#528)
  • TryFrom<&CCSDSRefFrame> for CelestialFrame and TryFrom<&CCSDSRefFrame> for ReferenceFrame: a reference-frame token alone no longer identifies a frame without its center; use odm_celestial_frame or FrameAxes::try_from. @duncaneddy (#536)
  • The Earth-only CENTER_NAME check in OPM::state_in_frame. @duncaneddy (#536)
  • brahe::ccsds::interop::state_for_segment and state_into_segment_axes; the frame returned by segment_celestial_frame carries the epoch, so state_in_frame on a trajectory performs the conversion they used to apply. @duncaneddy (#545)
  • The GCRF/EME2000-only covariance rule and its helpers (covariance_frame_allowed, inertial_covariance_rotation, frame_covariance_jacobian, frame_covariance_jacobian_6). @duncaneddy (#557)

Fixed

  • Fixed missing figures, CSV tables, and text output on the deployed documentation site for the NETWORK-flagged examples by adding a same-run artifact hand-off from the --network example run to the docs deploy, with releases now failing loud if the hand-off is absent instead of publishing incomplete docs. @duncaneddy (#419)
  • Fixed getting-started Rust example snippets that exposed the internal FLAGS directive or omitted the #![allow(unused_imports)] attribute needed to compile a copied block, normalizing all of them to show a crate-level allow attribute and hide the runner directive. @duncaneddy (#419)
  • Fixed the weekly Warm Brahe Data Cache workflow aborting on the horizons: manifest entry, which had no handler in scripts/warm_data_cache.py. @duncaneddy (#424)
  • brahe.cli current-epoch helper now uses UTC rather than local time when computing element-set age. @duncaneddy (#444)
  • Non-finite/non-numeric CLI expression results and unknown time_units in plot_estimation_* raise TypeError when the value's type is wrong (previously ValueError); the string-valued cases keep ValueError. @duncaneddy (#444)
  • Celestrak default cache TTL documentation stated 6 hours; the default is 2 hours. @duncaneddy (#454)
  • SBDB cache reads no longer swallow file read errors. @duncaneddy (#454)
  • Python CelestrakClient methods release the GIL while a request is in flight, so other Python threads can run during Celestrak downloads. @duncaneddy (#457)
  • parse_ccsds_datetime accepts the optional trailing Z terminator permitted by the CCSDS time-code formats. @duncaneddy (#464)
  • SpaceTrackClient releases the GIL during every request, auto-login, and rate-limit wait, so other Python threads keep running while a Space-Track call is in flight. @duncaneddy (#465)
  • Download errors for NAIF kernels, star catalogs, and the FES2004 model name the resource as well as the URL, including the BRAHE_NETWORK_MODE refusal message. @duncaneddy (#466)
  • The Celestrak client retries requests that fail with a DNS HostNotFound error, consistent with the other download paths. @duncaneddy (#466)
  • Loopback detection for BRAHE_NETWORK_MODE handles scheme-relative URLs. @duncaneddy (#466)
  • Covariance interpolation no longer fails on long arcs; the 2-Wasserstein cross term is formed through symmetric positive semi-definite square roots instead of a general square root of the covariance product, which the Denman-Beavers iteration cannot resolve past a condition number of roughly 1e10. The interpolated covariance is now symmetric by construction. @duncaneddy (#467)
  • sqrtm and sqrtm_dmatrix no longer reject well-converged square roots of large matrices; the convergence residual is measured against each column's own magnitude rather than a fixed absolute bound. @duncaneddy (#467)
  • Event detection during backward propagation no longer overflows the stack; the bisection bracket bounds are ordered by time rather than assuming forward propagation, and the refinement recursion is bounded. @duncaneddy (#468)
  • Backward propagation no longer skips event callbacks; detected events within a step are processed in the order propagation reaches them rather than always ascending in time. @duncaneddy (#468)
  • Fixed #461: Adaptive integrators (RKF45, RKF78, DP54, RKN1210) now grow their step size during propagation; abs_tol, rel_tol and max_step previously had no effect because every step-size increase suggested by the error controller was discarded, only allowing equal or smaller steps from the initial step. (Found by @carter-franz). @duncaneddy (#469)
  • CCSDS OEM, OMM, and OPM KVN writers now emit the header COMMENT lines before CLASSIFICATION, matching the fixed keyword order of the CCSDS 502.0-B-3 header tables. @duncaneddy (#474)
  • CCSDS OEM, OMM, and OPM XML parsers now collect repeated COMMENT elements instead of failing with duplicate field COMMENT. @duncaneddy (#476)
  • CCSDS OEM XML parsing now accepts messages with more than one <segment>, and OPM XML parsing accepts more than one <maneuverParameters>; both previously failed with a duplicate field error, which made brahe unable to re-read its own multi-segment OEM and multi-maneuver OPM XML output. @duncaneddy (#476)
  • CCSDS OEM, OMM, OPM, and CDM XML writers now escape &, <, and > in free-text fields, and " in the USER_DEFINED_* value attribute, so messages carrying those characters produce well-formed XML. @duncaneddy (#477)
  • CCSDS CDM XML parsing now resolves entity references in element text; previously any escaped & or < truncated the value to the fragment following the last entity. @duncaneddy (#477)
  • CCSDS CDM XML parsing now normalizes USER_DEFINED_* attribute values, which previously came back with their escapes intact. @duncaneddy (#477)
  • CCSDS CDM XML output now includes the covariance block's comments, which were dropped entirely. @duncaneddy (#477)
  • CCSDS OEM, OMM, OPM, and CDM writers now convert each epoch to the message's declared TIME_SYSTEM before formatting, per CCSDS 502.0-B-3 subsection 7.5.11 and CCSDS 508.0-B-1 subsection 6.2.3.4, instead of writing it in whichever time system the Epoch happened to carry. CREATION_DATE and all CDM time tags are written in UTC. @duncaneddy (#478)
  • CCSDS OEM, OMM, OPM, and CDM output is now stable across repeated write-and-reread cycles, covered by regression tests for all four message types in KVN, XML, and JSON. @duncaneddy (#479)
  • CCSDS CDM, OMM, and OPM KVN parsers now attribute each COMMENT to the section it introduces rather than to the preceding one, so a comment ahead of TCA is a relative-metadata comment and one ahead of OBJECT is that object's metadata comment. @duncaneddy (#480)
  • CCSDS CDM KVN round trips are now stable: write(parse(write(x))) == write(x) holds for CDMExample1 through CDMExample4 and CDMExample_issue_940, which previously migrated relative-metadata comments into the header on every cycle. @duncaneddy (#480)
  • CCSDS OEM, OMM, OPM, and CDM JSON writers now emit each block's comments, and the JSON readers restore them to the block they belong to; previously every comment was dropped on the first write to JSON. @duncaneddy (#481)
  • Interpolating an orbit or generic trajectory exactly at a repeated epoch now returns the stored state instead of NaN. Repeated epochs remain supported so an impulsive maneuver can hold both its pre- and post-burn state; the most recently added state at that epoch is returned. @duncaneddy. @duncaneddy (#482)
  • Even-point Lagrange interpolation now selects a window balanced around the query rather than one sample early, roughly halving interior interpolation error; two-point Lagrange previously excluded the upper bracketing sample and extrapolated. Odd point counts are unchanged. @duncaneddy. @duncaneddy (#482)
  • CCSDS OPM and OMM writers no longer emit an EPOCH in the covariance block, which CCSDS 502.0-B-3 tables 3-3 and 4-3 do not define. The stray keyword overwrote the state vector's epoch when the message was read back, silently moving the state. The OEM covariance block, where EPOCH is required, is unchanged. @duncaneddy (#484)
  • CCSDS OEM JSON output now includes covariance comments, and CDM JSON output includes the data-section comments; both were read but never written. @duncaneddy (#484)
  • CCSDS CDM XML parsing now reads CDATA sections, which were previously discarded. @duncaneddy (#484)
  • Example and documentation-plot CI runs no longer hang when Celestrak is unreachable. The groups were previously fetched live whenever no restorable cache existed, which was the normal case for a pull request branched from main, since every saved Celestrak cache was scoped to a ref other pull requests cannot read. @duncaneddy (#485)
  • CCSDS CDM JSON output now includes the OD parameters and additional parameters blocks, which were omitted entirely, so a CDM round-tripped through JSON no longer loses them. @duncaneddy (#486)
  • CCSDS CDM MIN_DV and MAX_DV are now written in KVN and XML; they were parsed but emitted by no encoder. @duncaneddy (#486)
  • CCSDS covariance values now round-trip across a write and reread. Written at full f64 precision they drifted by one unit in the last place per cycle, because the m²/km² conversion is not exactly invertible; they are rounded to fifteen significant digits at conversion. @duncaneddy (#486)
  • Epoch::to_datetime and Epoch::to_datetime_as_time_system now report the nanoseconds the epoch holds. Rounding to whole nanoseconds was counted twice, so a value was reported one nanosecond too large whenever it carried into the next second, and an epoch built on a whole second reported a fraction of a nanosecond that was not there. Reading and rewriting a date accumulated a nanosecond per cycle. @duncaneddy. @duncaneddy (#488)
  • A continuous-integration job that stops making progress, such as one waiting on an unreachable data mirror, now fails within a bounded time instead of occupying a runner until the six-hour default limit and delaying other work. @duncaneddy (#489)
  • Rust examples reuse a shared compiled dependency build between runs instead of rebuilding it for every example, which was previously prevented by generating each example at a new temporary path. @duncaneddy (#495)
  • The changelog validation check no longer reports a spurious failure on pull requests whose description is filled in just after creation, which is how the GitHub CLI and stacked-pull-request tooling create them. It now runs when a pull request is marked ready for review rather than while it is still a draft. @duncaneddy (#498)
  • The aggregate continuous-integration check no longer reports a failure when a run is superseded by a newer push. Previously any pull request that received a second push while its checks were still running displayed a failed status for the abandoned run. @duncaneddy (#500)
  • Continuous-integration jobs no longer compile the Rust extension twice. Dependency installation was building it under an isolation that could not reuse the cached compiler output, only for the following step to rebuild it. @duncaneddy (#503)
  • Fixed an intermittent failure in the SpaceTrack GIL test, whose mock server closed each connection while advertising a reusable one, causing the client to send the query on a socket the server had already abandoned. @duncaneddy (#510)
  • CCSDS ODM and CDM XML writing now rejects text carrying a character XML 1.0 forbids (the C0 controls other than tab, line feed, and carriage return, along with U+FFFE and U+FFFF) with an error naming the element and code point, instead of emitting a document that no conforming XML parser will read. @duncaneddy (#511)
  • Fixed utils::threading::tests::test_get_max_threads failing intermittently: it asserted that the configured thread count is at most the CPU count, which set_num_threads deliberately permits callers to exceed, against global state other tests in the module leave oversubscribed. @duncaneddy (#513)
  • Epoch::from_datetime and Epoch::to_datetime now round-trip in UT1, TDB, TCB, and TCG; previously an epoch built from a whole second in one of these systems read back up to 693 ns away from it and drifted further on each rebuild. @duncaneddy (#513)
  • Epoch::to_datetime reports the whole second an epoch was built on rather than the preceding second carrying a nanosecond count just short of a full second, and no longer clamps a value lying within half a nanosecond of the next second onto that boundary. @duncaneddy (#513)
  • UTC epochs before 1972, when UTC ran at a rate offset from TAI rather than a whole number of seconds, now report the date they were built from; previously Epoch::to_datetime returned up to 0.108 s early and rebuilding from the reported fields displaced the epoch by that amount. @duncaneddy (#514)
  • UTC epochs on a day carrying a leap second now report the second they were built on throughout the day; previously every epoch past midday on such a day reported one second late. @duncaneddy (#514)
  • time_system_offset returns the UTC offset in force at the instant it is given, rather than the entry TAI - UTC seconds later, correcting conversions within a leap second and across the pre-1972 rate offset. @duncaneddy (#514)
  • Converting from UT1 no longer shifts the leap-second lookup by up to 0.9 days by subtracting a UT1-UTC offset expressed in seconds from a fractional day. @duncaneddy (#514)
  • Fixed intermittent failures in the Rust unit-test suite caused by tests without an explicit #[serial] or #[parallel] annotation running concurrently with serial tests and mutating process-global state (the EOP, gravity and space weather providers, the SPICE kernel registry, the frame and object registries, the thread pool, and environment variables) out from under them. @duncaneddy (#516)
  • KeplerianPropagator with an EME2000 output frame no longer applies a spurious frame rotation in state_gcrf, and its ECEF/ITRF output arms no longer disagree. @duncaneddy (#527)
  • Frame conversion of a Keplerian trajectory declared in EME2000 now converts the elements about the correct frame instead of treating them as GCRF elements, matching the point-query accessors. @duncaneddy (#527)
  • The OPM maneuver propagation example no longer treats a TOD state vector as ECEF. @duncaneddy (#528)
  • Test fixtures set_test_static_eop (src/frames/gcrf_itrf.rs) and static_eop (tests/test_frames.py) apply the SOFA cookbook dX/dY celestial pole offsets at their true scale; new test_bias_precession_nutation_applies_cip_offsets tests assert the offsets enter the CIP with the correct sign and magnitude. @duncaneddy (#531)
  • wrap_to_2pi and the right-ascension conversions return values strictly in [0, 2 pi); an input a few 1e-17 below zero no longer maps to exactly 2 pi. @duncaneddy (#532)
  • PR Tests now filters build-irrelevant pull requests inside the workflow instead of with a trigger-level paths-ignore, so the required ci-success check is always reported rather than left pending indefinitely. @duncaneddy (#544)
  • The shared RateLimiter queues concurrent reservations one slot apart instead of waking every blocked caller at the same time, so download_all workers and Space-Track callers cannot burst past the configured cap. @duncaneddy (#554)
  • Check out the repository before setting up the Rust toolchain in the test-python and integration-python CI jobs so the Rust build cache restore can find Cargo.toml and compute a matching cache key. @duncaneddy (#567)
  • The NAIF, ICGEM, SBDB/Horizons and star catalog caches on main are no longer evicted between the weekly warm run and the scheduled integration run. @duncaneddy (#568)
  • The Windows and macOS Rust integration jobs run cargo test --tests, which skips doctests and keeps each attempt within the 30-minute retry cap. @duncaneddy (#569)
  • The Python 3.10 integration job fails when pytest fails instead of being masked by the coverage report command that follows it. @duncaneddy (#569)
  • Allow the linker_messages lint on macOS builds so the compact-unwind note about the brahe test binary's __eh_frame section does not fail the build under cargo's build.warnings = "deny". @duncaneddy (#582)
  • Fix clippy::clone_on_copy errors reported by Rust 1.99 clippy in the Python bindings and event-type tests. @duncaneddy (#586)
  • Fix block_diagonal, accel_gravity_spherical_harmonics, accel_gravity_spherical_harmonics_clenshaw, accel_gravity_spherical_harmonics_cunningham, accel_drag, and accel_drag_for_body silently transposing 3x3 matrix arguments passed as Fortran-ordered arrays or transposed views, and accept non-contiguous 3x3 views instead of raising ValueError. @duncaneddy (#593)

[1.7.0] - 2026-07-21

Added

  • Native SPICE kernel support: SPK and BPCK readers (DAF container, SPK Type 2/3, binary PCK Type 2), usable standalone or via the global registry. @duncaneddy (#376)
  • Global multi-kernel registry: load_kernel, unload_kernel, clear_kernels, loaded_kernels; kernels stay resident simultaneously with most-recently-loaded precedence and epoch-aware chain fallback. @duncaneddy (#376)
  • NAIFId enum (planets, planetary-system barycenters, major moons, Id(i32) catch-all) and FrameId enum (MoonPaDe440 = 31008 + Id(i32)); all query functions take impl Into<NAIFId> / impl Into<FrameId>, so raw integer IDs keep working. Python mirrors as NAIFId/FrameId IntEnums. @duncaneddy (#376)
  • SPICEKernel enum covering every downloadable kernel — DE (de430–de442s), JPL satellite ephemeris kernels (mar099, mar099s, jup365, sat441, ura184, nep097, plu060), and binary PCK (moon_pa_de440) — plus KernelSource for bring-your-own kernel paths. @duncaneddy (#376)
  • load_common_kernels() (de440s + moon_pa_de440, ~46 MB) and load_all_kernels() (~2.5 GB) pre-initialization helpers. @duncaneddy (#376)
  • Generic NAIF-ID ephemeris queries: spk_position, spk_velocity, spk_state (pooled, cross-kernel chaining) and kernel-scoped spk_position_from_kernel / spk_velocity_from_kernel / spk_state_from_kernel. @duncaneddy (#376)
  • Per-body kernel-backed accessors {body}_{position,velocity,state}_spice for Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, and the SSB. The five outer planets return true body centers via a two-leg DE + satellite-ephemeris-kernel sum (kernel auto-downloaded on first use: mar099s ~68 MB, jup365 ~1.1 GB, sat441 ~662 MB, ura184 ~387 MB, nep097 ~105 MB); {planet}_barycenter_{position,velocity,state}_spice variants provide the planetary-system barycenter from the DE kernel alone. @duncaneddy (#376)
  • PCK orientation queries with typed attitude returns: pck_euler_angle (EulerAngle, ZXZ), pck_euler_angle_and_rates, pck_euler_rates, pck_quaternion (Quaternion), pck_rotation_matrix (RotationMatrix), plus the raw pck_euler_angles. @duncaneddy (#376)
  • Epoch::spice_et() — SPICE ephemeris time (TDB seconds past J2000) convenience accessor; Epoch::seconds_past_j2000_as_time_system for other time systems. @duncaneddy (#376)
  • datasets::naif::download_spice_kernel for any known NAIF kernel (DE, satellite ephemeris, binary PCK), exposed in Python as datasets.naif.download_spice_kernel. @duncaneddy (#376)
  • ANISE validation suite (mm-level matched-ET comparisons, network-gated lunar DCM validation) and native-vs-ANISE criterion benchmarks. @duncaneddy (#376)
  • Learn/API documentation for the spice module and four standalone examples, enabled in CI with cached kernels. @duncaneddy (#376)
  • Lunar reference frames LCI/LFPA/LFME and Mars frames MCI/MCMF with {rotation|position|state}_{from}_to_{to} transformations, cross-center state_eci_to_lci/state_eci_to_mci, and automatic moon_pa_de440 PCK loading. @duncaneddy (#377)
  • Native IAU/WGCCRE 2015 body-rotation engine (rotation_icrf_to_body_fixed_iau, iau_rotation_model_ids) with an embedded 17-body coefficient table transcribed from pck00011.tpc and validated against ANISE. @duncaneddy (#377)
  • Centralized ReferenceFrame enum and frame router (rotation_frame_to_frame, position_frame_to_frame, state_frame_to_frame) with generic NAIF-ID variants (BodyCenteredICRF, BodyFixedIAU, BodyFixedPCK) and "ECI"/"ECEF" string aliases. @duncaneddy (#377)
  • CentralBody abstraction (Earth, Moon, Mars, EMB, SSB, Custom) with from_naif_id(), and ForceModelConfig::for_body(), lunar_default() (GRGM660PRIM 50×50, Earth+Sun third bodies, Moon+Earth occultation), mars_default() (GMM-2B 50×50, exponential drag, Mars occultation), and cislunar_default() (EMB-centered) constructors. @duncaneddy (#377)
  • Barycentric (EMB/SSB) propagation with correct direct/differential third-body handling; ThirdBody gains Earth, Phobos, Deimos, and Custom { name, naif_id, gm } variants. @duncaneddy (#377)
  • Multi-body eclipse modeling via occulting_bodies on the SRP configuration and new OccultingBody type. @duncaneddy (#377)
  • Body-generic dynamics functions: accel_third_body_for_body, eclipse_conical_for_body, eclipse_cylindrical_for_body, accel_relativity_for_body, accel_drag_for_body, state_eci_to_koe_for_body. @duncaneddy (#377)
  • state_bci, state_bcbf, and state_in_frame(frame, epoch) on the orbit state-provider traits (DOrbitStateProvider/SOrbitStateProvider), implemented on the numerical, Keplerian, and SGP propagators and both trajectory types. @duncaneddy (#377)
  • OrbitFrame::BodyCenteredInertial(center): non-Earth propagator trajectories carry their center's NAIF ID, and trajectory Earth-frame conversions (point queries, batch to_*, covariance, CCSDS OEM export) re-center through the frame router; state_koe_osc returns elements about the trajectory's own center using that body's GM. @duncaneddy (#377)
  • User-defined body-fixed frames: register_custom_frame(key, rotation, omega=None) + ReferenceFrame::BodyFixedCustom{center, key} (epoch->DCM callback with optional angular-velocity callback; numeric transport-term fallback), with a runnable example. @duncaneddy (#377)
  • state_koe_to_eci_for_body (inverse of state_eci_to_koe_for_body), ReferenceFrame::ECI/ECEF aliases, kernel_is_loaded(), and the SECONDS_PER_JULIAN_CENTURY constant. @duncaneddy (#377)
  • just setup recipe for local development (uv venv, pre-commit hooks, and pre-downloading the de440s/moon_pa_de440/mar099s kernels tests expect cached). @duncaneddy (#377)
  • NAIF mar099s Mars satellite kernel support (download_satellite_kernel, load_kernel("mar099s")) for Phobos/Deimos ephemerides. @duncaneddy (#377)
  • Physical constants R_MARS, OMEGA_MARS, OMEGA_MOON, GM_PHOBOS, GM_DEIMOS. @duncaneddy (#377)
  • ForceModelConfig::validate() with clear errors for invalid central-body/force-model combinations, called automatically at propagator construction. @duncaneddy (#377)
  • Python bindings for all of the above (ReferenceFrame, CentralBody, OccultingBody, extended ThirdBody, config constructors, frame functions, state_in_frame). @duncaneddy (#377)
  • CI caching of kernel and gravity-model downloads across jobs and workflow runs, with a weekly keep-warm workflow. @duncaneddy (#377)
  • Learn/API documentation pages and runnable lunar/Mars propagation examples (Python + Rust). @duncaneddy (#377)
  • Clenshaw-summation spherical-harmonic gravity kernel (GravityModel::compute_spherical_harmonics_clenshaw, accel_gravity_spherical_harmonics_clenshaw), with serial and parallel (order-parallel, bitwise-identical) execution — Rust + Python. @duncaneddy (#378)
  • GravityModelCoefficients load configuration (Clenshaw default / Cunningham / Both) with from_model_type_with_coefficients / from_file_with_coefficients constructors (Rust + Python) and load_uncached_with_coefficients (Rust only, matching load_uncached), plus precompute_* / drop_* / has_* coefficient-set lifecycle methods — Rust + Python. @duncaneddy (#378)
  • GravityModel::get_c(n, m) / get_s(n, m) single-coefficient accessors — Rust + Python. @duncaneddy (#378)
  • accel_gravity_spherical_harmonics_cunningham convenience function — Rust + Python. @duncaneddy (#378)
  • High-precision reference tests: Clenshaw kernel pinned against independent 40-digit mpmath evaluations of EGM2008_360 to 1e-13 relative at degrees 120–200, with the generator committed as scripts/generate_clenshaw_gravity_reference.py. @duncaneddy (#378)
  • The Cunningham kernel returns a descriptive error (instead of silent NaN) on denormalized-recursion overflow. @duncaneddy (#378)
  • set_global_gravity_model Python binding (mirrors the existing Rust function). @duncaneddy (#378)
  • Clenshaw-vs-Cunningham benchmark suite in benchmarks/gravity_benchmarks.rs. @duncaneddy (#378)
  • is_empty() method on StaticEOPProvider, FileEOPProvider, and CachingEOPProvider (Python bindings), satisfying clippy's len_without_is_empty for the newly workspace-covered brahe-py crate. @duncaneddy (#380)
  • Solid Earth tide accelerations for numerical propagation per IERS Conventions (2010) TN36 §6.2.1: frequency-independent lunisolar corrections to degree-2/3 geopotential coefficients with degree-4 coupling (Eqs. 6.6–6.7, anelastic Love numbers from Table 6.3), and optional frequency-dependent corrections from Tables 6.5a/b/c via SolidTideConfig(frequency_dependent=True). @duncaneddy (#385)
  • TidesConfiguration and PermanentTideConfig (Auto/ConvertTo(system)/Off) on ForceModelConfig.tides to enable tidal corrections and control permanent-tide normalization of the static field's C̄20 (IERS TN36 §6.2.2); tides remain disabled by default in all force-model presets. @duncaneddy (#385)
  • GravityModel.convert_tide_system (Rust and Python) to convert a gravity model's C̄20 between mean-tide, zero-tide, and conventional tide-free systems. @duncaneddy (#385)
  • Rust API accel_solid_earth_tides / solid_earth_tide_coefficients in orbit_dynamics::tides for direct evaluation of tide accelerations and coefficient corrections. @duncaneddy (#385)
  • Warnings for tide configurations that double-count the permanent tide (zero-tide/mean-tide conversion combined with solid tides, or shared global gravity models): Rust warning at propagator construction, suppressible UserWarning in Python. @duncaneddy (#385)
  • Learn documentation page on tidal corrections (tide systems, permanent tide handling, solid Earth tides) and runnable Python/Rust examples: force_model_tides, tides_permanent_only, tides_static, tides_static_time_varying. @duncaneddy (#385)
  • ReferenceFrame.EMR, ReferenceFrame.SER, and ReferenceFrame.GSE synodic frames (NASA TP-20220014814) supported across the frame router (rotation/position/state_frame_to_frame, state_in_frame), with exact rotation-matrix time derivatives (GTDS/STK convention) for the velocity transform. @duncaneddy (#393)
  • Pairwise synodic transforms rotation/position/state_gcrf_to_{emr,ser,gse} and inverses in Rust and Python. @duncaneddy (#393)
  • Native SPK acceleration evaluation via analytic Chebyshev differentiation: spk_acceleration and spk_acceleration_from_kernel (Rust and Python). @duncaneddy (#393)
  • SUN_EARTH_BARYCENTER_ID synthetic frame-center ID with GM-weighted Sun-Earth barycenter resolution in the router's translation seam. @duncaneddy (#393)
  • Batch state-provider methods states_bci, states_bcbf, and states_in_frame on SOrbitStateProvider/DOrbitStateProvider and on the SGPPropagator, KeplerianPropagator, and NumericalOrbitPropagator Python classes. @duncaneddy (#393)
  • Documentation: Synodic Reference Frames Learn page and Library API page, frame/kernel-requirement table entries, and SPK acceleration API docs. @duncaneddy (#393)
  • Per-body third-body configuration: ThirdBodyConfiguration pairs each perturber with its own ephemeris_source and gravity model; SphericalHarmonic and EarthZonal fields are evaluated at the object's position relative to the perturbing body in that body's fixed frame, with a point-mass indirect term. @duncaneddy (#394)
  • Public accel_third_body_field_for_body (Rust + Python) evaluating a configured gravity model (point-mass, spherical-harmonic, or Earth-zonal) for a perturbing body about any central body. @duncaneddy (#394)
  • ThirdBody planetary-system barycenter variants MarsBarycenter–NeptuneBarycenter (NAIF 4–8, system GMs), used by the default Earth force models; accel_third_body accepts both barycenter and planet-center variants (the latter auto-load their satellite-system kernels). @duncaneddy (#394)
  • DragConfiguration.body to attribute drag to a non-central body: density and relative wind are evaluated at the object's state relative to that body (enables Earth drag on EMB-centered propagations). @duncaneddy (#394)
  • GravityConfiguration::Zero — explicit no-central-gravity term for barycentric propagation centers (used by cislunar_default). @duncaneddy (#394)
  • ECI↔EMBI frame translation helpers position_eci_to_emb/position_emb_to_eci and state_eci_to_emb/state_emb_to_eci (Rust + Python). @duncaneddy (#394)
  • Constants GM_MARS_SYSTEM–GM_NEPTUNE_SYSTEM and GM_PLUTO_SYSTEM (planetary-system barycentric GMs, NAIF gm_de440.tpc). @duncaneddy (#394)
  • ThirdBody::as_central_body() and ThirdBody::body_fixed_frame() helpers (Rust + Python). @duncaneddy (#394)
  • Standalone example numerical_propagation/cislunar_earth_forces (EMB-centered propagation with Earth-attributed forces). @duncaneddy (#394)
  • Epoch.to_time_system() (Rust and Python) returns a new Epoch for the same instant expressed in a different time system. The instant is unchanged, so the returned epoch compares equal to the original; only the scale it reports in differs. The method had been referenced by the Epoch::time_system documentation but was never implemented. @duncaneddy (#395)
  • TimeSystem API reference page under library_api/time/, documenting all ten time systems (GPS, TAI, TT, UTC, UT1, TDB, TCG, TCB, BDT, GST). It previously lived under constants/units.md and listed only five, so TDB, TCG, TCB, BDT, and GST did not render at all. @duncaneddy (#395)
  • "Time System" section in the Learn Epoch guide, covering how an epoch's time system is set and read, and the distinction between converting an epoch and projecting one, with runnable Rust and Python examples. @duncaneddy (#395)
  • Example fixtures covering FLAGS declared past the tenth line of a file. @duncaneddy (#395)
  • FES2004 ocean tide model (OceanTideModel, OceanTideConfig) with admittance-wave expansion per IERS TN36 §6.3, configurable degree/order up to 100, and one-time cached download of the IERS coefficient file. @duncaneddy (#397)
  • Solid Earth pole tide (SolidTideConfig.pole_tide) and ocean pole tide (OceanTideConfig.pole_tide) per IERS TN36 §6.4/§6.5 with the IERS 2018 linear secular pole model (secular_pole, wobble_parameters, solid_earth_pole_tide_deltas, ocean_pole_tide_deltas). @duncaneddy (#397)
  • TidesConfiguration.ocean field enabling ocean tides in the numerical propagator force model; ForceModelConfig::high_fidelity() now enables solid tides with frequency-dependent corrections, both pole tides, and 30×30 ocean tides. @duncaneddy (#397)
  • Python bindings for OceanTideConfig and the new pole_tide / ocean configuration fields, with regenerated type stubs. @duncaneddy (#397)
  • ReferenceFrame.Synodic(origin, primary, secondary) and SynodicOrigin for arbitrary two-body rotating frames, with synodic_origin/synodic_primary/synodic_secondary accessors (also populated for EMR/SER/GSE). @duncaneddy (#399)
  • synodic_barycenter_id and generalized synthetic-barycenter resolution in the frame router (Sun-Earth barycenter is now a special case of the generic encoding). @duncaneddy (#399)
  • Solar System Scope planet texture registry with download_planet_texture and generalized load_body_texture (CC BY 4.0, credited in the license page). @duncaneddy (#399)
  • central_body and additional_bodies parameters on plot_trajectory_3d for Moon/Mars/custom-body scenes, backed by a body-visuals registry. @duncaneddy (#399)
  • plot_synodic_3d and plot_earth_moon_rotating_3d for rotating-frame trajectory visualization. @duncaneddy (#399)
  • Worked examples: LRO lunar orbit, Earth-Moon free return, MRO Mars orbit, Dawn at Ceres (user-defined central body), with learn/API documentation for the new frames and plotting. @duncaneddy (#399)
  • Standalone Rx/Ry/Rz matrix functions. @duncaneddy (#399)
  • Planetary radius constants (R_MERCURY .. R_NEPTUNE). @duncaneddy (#399)
  • Tested learn examples for generic ReferenceFrame.Synodic frames and plot_earth_moon_rotating_3d. @duncaneddy (#399)
  • Keplerian ↔ equinoctial element conversions (state_koe_to_equinoctial, state_equinoctial_to_koe) following the Vallado (eq. 2-99) formulation with Montenbruck a, h, k, p, q, l naming and an explicit retrograde factor (Rust + Python). @duncaneddy (#403)
  • MeanElementMethod selector (BROUWER_LYDDANE analytical, numerical(config) windowed averaging) plus WindowAlignment, WindowEdgeHandling, MeanElementNumericalMethodConfig, and MeanElementInverseConfig configuration types (Rust + Python). @duncaneddy (#403)
  • Numerical osculating → mean conversion via windowed averaging in equinoctial space, with trapezoidal time-weighting of the slow elements and configurable window length (seconds), alignment (centered / trailing / leading), and edge handling (truncate / preserve-window). @duncaneddy (#403)
  • Numerical mean → osculating conversion via an iterative differential-correction inverse driven by a supplied force model and numerical propagation configuration. @duncaneddy (#403)
  • Batch conversion functions batch_state_koe_osc_to_mean and batch_state_koe_mean_to_osc returning (epoch, state) pairs, with Python bindings returning (list[Epoch], ndarray). @duncaneddy (#403)
  • FK5, Hipparcos, and Tycho-2 star catalog datasets with cached downloads, typed records, StarRecord trait, filtering, and Polars DataFrame conversion (Rust + Python). @duncaneddy (#404)
  • RA/Dec coordinate transformations: position_radec_to_inertial/position_inertial_to_radec, state_radec_to_inertial/state_inertial_to_radec, position_radec_to_azel/position_azel_to_radec. @duncaneddy (#404)
  • Rigorous proper-motion epoch propagation apply_proper_motion (delegating to IAU SOFA iauPmsafe via rsofa; theory per ESA SP-1200 §1.5.5) and StarRecord::radec_at_epoch. @duncaneddy (#404)
  • Star-field sensor simulation example with animated 3D and sensor-frame Plotly visualizations. @duncaneddy (#404)
  • datasets::ssn_sensors::load_ssn_sensors / bh.datasets.ssn_sensors.load(): embedded dataset of 21 representative SSN sensor sites from Vallado 4th Ed. Tables 4-2/4-3/4-4 with locations, az/el/range field-of-view limits, and bias/noise calibration values. @duncaneddy (#406)
  • AzElRangeMeasurementModel: topocentric azimuth/elevation/range measurement model with constant-bias support, azimuth residual wrapping, and a wrap-aware finite-difference Jacobian. @duncaneddy (#406)
  • SimpleSSNSensor: ground-sensor measurement simulation with wrap-aware field-of-view checks, seeded Gaussian noise, step-wise (measure) and batched (simulate_observations) generation, and a matching filter-side measurement_model(). @duncaneddy (#406)
  • MeasurementModel::residual(): overridable, dimension-validated residual hook used by all EKF/UKF/BLS residual computations. @duncaneddy (#406)
  • ExtendedKalmanFilter::propagate_to / UnscentedKalmanFilter::propagate_to: measurement-free prediction step that records covariance growth across tracking gaps. @duncaneddy (#406)
  • SSN tracking examples: standalone Rust/Python pair (examples/estimation/ssn_tracking) and a six-hour BLS/EKF/UKF walkthrough with sensor-network, measurement, and filter-comparison figures (docs/examples/ssn_tracking.md). @duncaneddy (#406)
  • brahe transform position and brahe transform rotation CLI subcommands for position-vector and rotation-matrix transforms between reference frames. @duncaneddy (#407)
  • brahe transform frame now accepts all named reference frames (GCRF, ITRF, EME2000, lunar LCI/LFPA/LFME, Mars MCI/MCMF, EMBI, SSBI, synodic EMR/SER/GSE) in addition to the ECI/ECEF aliases. @duncaneddy (#407)
  • Runnable fk5_frame_correction example (Python and Rust) demonstrating the star-catalog to EME2000 to GCRF to ITRF transformation chain. @duncaneddy (#413)
  • builder() constructors for DNumericalOrbitPropagator, DNumericalPropagator, SGPPropagator (OMM elements), WalkerConstellationGenerator, ExtendedKalmanFilter, UnscentedKalmanFilter, BatchLeastSquares, AccessProperties, and CDMObjectMetadata, with Python mirrors for all but CDMObjectMetadata. @duncaneddy (#414)
  • WalkerConstellationGeneratorBuilder::build() returns an error on invalid t/p/f configurations instead of panicking. @duncaneddy (#414)
  • CDMObjectMetadataBuilder exposes the optional CCSDS CDM metadata fields that CDMObjectMetadata::new() could not set, and enforces the conditional-mandatory rules (ALT_COV_TYPE requires ALT_COV_REF_FRAME; EPHEMERIS_NAME=ODM requires ODM_MSG_LINK). @duncaneddy (#414)
  • Builder-focused documentation: Learn-page sections, library API pages, and runnable Rust/Python examples for each builder. @duncaneddy (#414)
  • JPL Small-Body Database (SBDB) Lookup client (brahe.datasets.sbdb.SBDBClient returning SBDBObject) that resolves a name or designation to its NAIF/SPK ID and SI physical parameters (GM in m³/s², radius in m), with on-disk caching. @duncaneddy (#416)
  • JPL Horizons SPK client (brahe.datasets.horizons: HorizonsClient, HorizonsSPKRequest, HorizonsSPKResponse) that generates, caches, and loads targeted small-body SPK kernels, with a response handle exposing the cached .bsp path, raw bytes, and a direct load into the SPICE registry. @duncaneddy (#416)
  • SPK segment type 21 (Extended Modified Difference Arrays) reader, enabling Horizons-generated small-body SPKs (e.g. Ceres) to load and answer position, velocity, and state queries; the interpolation is a port of CSPICE spke21 validated bit-exact against CSPICE spkgeo on a real Ceres kernel. @duncaneddy (#416)
  • Learn and Library-API documentation pages for the SBDB and Horizons clients. @duncaneddy (#416)

Changed

  • Breaking: third-body ephemeris accessors renamed from *_de to *_spice (e.g. sun_position_de → sun_position_spice, accel_third_body_sun_de → accel_third_body_sun_spice) — the ephemeris source is the SPICE kernel system (DE + satellite ephemeris kernels), not DE alone. @duncaneddy (#376)
  • Breaking: the five outer-planet accessors return the true planet body center (previously the planetary-system barycenter); the barycenter is available via the new *_barycenter_*_spice functions, and third-body force models use those (numerically identical to before, no satellite-kernel downloads in propagation). @duncaneddy (#376)
  • Breaking: per-body position outputs no longer apply a J2000→ICRF frame-bias rotation. NAIF documents that SPICE "J2000" output from DE kernels is already ICRF-aligned, so the previous rotation introduced a ~23 mas systematic offset (~1.7e4 m at 1 AU). @duncaneddy (#376)
  • Breaking: EphemerisSource::SPK payload type is SPICEKernel (was SPKKernel); serde variant names for the DE kernels are unchanged, so serialized force-model configs remain compatible. @duncaneddy (#376)
  • Switching between DE kernels no longer reloads global state; both kernels stay resident in the registry. @duncaneddy (#376)
  • ForceModelConfig gains a central_body field (serde-default Earth; existing configs and constructors unchanged) and its validity is now checked at propagator construction instead of failing mid-propagation. @duncaneddy (#377)
  • SolarRadiationPressureConfiguration gains occulting_bodies (serde-default [Earth], preserving existing behavior). @duncaneddy (#377)
  • state_koe_osc on the numerical propagator now computes osculating elements about the propagator's central body (Earth behavior unchanged; barycenter central bodies return an error). @duncaneddy (#377)
  • CentralBody::Mars and the MCI/MCMF frames are centered on the Mars body center (NAIF 499), with the body-center leg auto-loaded from the mar099s satellite ephemeris (the default DE kernel loads first so a satellite kernel cannot suppress the auto-initialization); ThirdBody::Mars keeps the system barycenter (NAIF 4) per the standard third-body formulation. @duncaneddy (#377)
  • The lunar PCK / Mars SPK auto-load guards latch on first use (OnceLock): the kernel registry is consulted once, and unloading kernels mid-run is no longer re-detected (failed loads retry). @duncaneddy (#377)
  • Kernel-only tests and doctests are no longer integration-gated: SPICE kernels are expected cached locally (just setup) and in CI; the pytest integration marker is reserved for remote-host access and long runtimes. @duncaneddy (#377)
  • Rust ThirdBody no longer derives Eq/Hash (the new Custom variant carries a float GM); the Python ThirdBody/CentralBody/OccultingBody/ReferenceFrame types are plain wrapper classes — equality is preserved but they are not hashable and class attributes are not singletons (use ==, not is). @duncaneddy (#377)
  • compute_spherical_harmonics and accel_gravity_spherical_harmonics now evaluate via the Clenshaw kernel when its coefficients are present (the default), falling back to Cunningham coefficients when those are the only set loaded. @duncaneddy (#378)
  • Gravity model loading now precomputes only Clenshaw coefficients by default (use GravityModelCoefficients::Both/Cunningham for the V/W kernel); set_max_degree_order rebuilds whichever coefficient sets exist. @duncaneddy (#378)
  • The numerical orbit propagator evaluates spherical-harmonic gravity through the Clenshaw kernel (the per-propagator V/W workspace matrices were removed); its per-propagator rotation cache is now an lru-crate LRU keyed on stage time. @duncaneddy (#378)
  • CLENSHAW_PARALLEL_THRESHOLD_NMAX tuned to 180 from benchmarks (with a TODO to derive it from the execution environment); Cunningham's documented numerical limits added to its rustdoc. @duncaneddy (#378)
  • GravityModel::compute_spherical_harmonics_with_workspace and accel_gravity_spherical_harmonics_with_workspace — renamed to compute_spherical_harmonics_cunningham_with_workspace / accel_gravity_spherical_harmonics_cunningham_with_workspace (the Clenshaw kernel needs no workspace). The old allocating Cunningham entry point is now GravityModel::compute_spherical_harmonics_cunningham (compute_spherical_harmonics keeps its name and signature but dispatches). No deprecation aliases retained. @duncaneddy (#378)
  • Marked additional space-weather tests and integration to issues running locally if celestrak is unavailable. @duncaneddy (#378)
  • Numerical propagator construction now applies the configured permanent-tide conversion to propagator-owned gravity models when ForceModelConfig.tides is set (models load with C̄20 exactly as published when tides are not configured). @duncaneddy (#385)
  • Breaking: ForceModelConfig.third_body becomes Option<Vec<ThirdBodyConfiguration>> with per-body entries. The field deserializes from bare bodies, a single entry, a list, or the legacy pre-flattening object shape, which migrates on load with its original semantics (pre-split planet names map to the *Barycenter variants they then denoted). @duncaneddy (#394)
  • Breaking: ThirdBody::Mars–Neptune now denote planet centers (NAIF 499–899, planet-only GMs, resolved through satellite-system kernels) instead of system barycenters; use the new *Barycenter variants for the previous behavior. @duncaneddy (#394)
  • Breaking: GM_MARS–GM_NEPTUNE and GM_PLUTO now hold planet-only GMs from NAIF gm_de440.tpc; the previous DE430-era system values moved to GM_*_SYSTEM and were updated to DE440 (full-precision tpc digits). @duncaneddy (#394)
  • Force-model validation keys the Earth-atmosphere-model and radius/spin drag requirements on the attributed body rather than the central body, and validates per-third-body gravity models (EarthZonal requires ThirdBody::Earth; SphericalHarmonic requires a body with a known body-fixed frame). An attributed drag body's ephemeris is verified at propagator construction. @duncaneddy (#394)
  • Renamed the IGNORE example flag to NETWORK, completing the split begun in #366. IGNORE named no reason to skip, while every file carrying it depends on a live third-party service. --ignore becomes --network on just test-examples and just make-plots. @duncaneddy (#395)
  • Reclassified plots/fig_access_benchmark.py and plots/fig_comparative_benchmarks.py from IGNORE to MANUAL. Neither is network-driven: both regenerate committed artifacts and require a custom environment, and neither is driven by just make-plots. @duncaneddy (#395)
  • Aligned the User Guide and Python API Reference navigation to a single section ordering and vocabulary. @duncaneddy (#395)
  • The Learn time-scale table and the TimeSystem overview rustdoc now document all ten time systems rather than five. @duncaneddy (#395)
  • All tidal corrections (solid Earth Step 1/2, pole tides, ocean tides) and the static gravity field are now evaluated in a single fold-in Clenshaw pass per dynamics call, replacing the separate solid-tide evaluation path. @duncaneddy (#397)
  • Bundled EGM2008 gravity model truncated from degree/order 360 to 120 and renamed GravityModelType::EGM2008_360 → EGM2008_120 to keep the crates.io package within size limits (bit-identical coefficients through degree 120; higher degrees available via ICGEM). @duncaneddy (#397)
  • Ocean tide force-model documentation (docs/learn/orbital_dynamics/tides.md) expanded to cover ocean tides, admittance waves, pole tides, and the download/cache behavior. @duncaneddy (#397)
  • Documentation is now served from https://docs.brahe.space/, with brahe.space and www.brahe.space redirecting to it. Existing duncaneddy.github.io/brahe/ links continue to resolve through GitHub's permanent redirect. @duncaneddy (#398)
  • Nightly development wheels now install from https://docs.brahe.space/simple/. @duncaneddy (#398)
  • The git clone commands in the installation guide now pass -c core.symlinks=true and clone only the main branch. @duncaneddy (#398)
  • plot_trajectory_3d: earth_texture renamed to texture, show_earth renamed to show_body, and all parameters after trajectories are now keyword-only (breaking; all in-repo usages updated). @duncaneddy (#399)
  • load_earth_texture removed in favor of load_body_texture. @duncaneddy (#399)
  • just download-resources now also warms the moon/mars/ceres textures. @duncaneddy (#399)
  • state_koe_to_eci_for_body/state_eci_to_koe_for_body renamed to state_koe_to_inertial_for_body/state_inertial_to_koe_for_body, taking a CentralBody instead of a bare gm and referencing elements to the body's mean equator at J2000 instead of the ICRF axes. @duncaneddy (#399)
  • Live-API examples now NETWORK-flagged. @duncaneddy (#399)
  • Breaking: state_koe_osc_to_mean and state_koe_mean_to_osc now take a MeanElementMethod argument (before angle_format) and return a Result (Rust) / raise on error (Python). Existing analytical behavior is preserved by passing MeanElementMethod::BrouwerLyddane / bh.MeanElementMethod.BROUWER_LYDDANE. @duncaneddy (#403)
  • Upgraded Rust dependency version requirements to latest compatible releases (cargo upgrade). @duncaneddy (#404)
  • UKF measurement statistics use a reference-point mean and residual-based deviations, making the measurement update robust for angular measurements near a wrap (identical results for non-angular models). @duncaneddy (#406)
  • EKF/UKF/BLS pre-fit and post-fit residuals now route through MeasurementModel::residual() (default behavior unchanged). @duncaneddy (#406)
  • rand/rand_distr promoted from dev-dependencies to runtime dependencies for measurement noise sampling. @duncaneddy (#406)
  • brahe transform frame dispatches through the core frame router (state_frame_to_frame) instead of a hardcoded ECI↔ECEF path. @duncaneddy (#407)
  • Rewrote docs/examples note admonitions as per-example API deep dives covering GPRecord.to_sgp_propagator, location_accesses, datasets.groundstations.load, AccessPropertyComputer, TimeEvent callbacks, additional_dynamics/control_input, ConstraintAll, and AOIExitEvent. @duncaneddy (#409)
  • Docs deploys now restore NETWORK-example figures saved by the weekly --network integration run, so example pages no longer reference figures that were never generated. @duncaneddy (#409)
  • Star-catalog documentation now describes each catalog's native reference frame (Hipparcos and Tycho-2 on ICRS axes, FK5 realizing EME2000) and when the ~23 mas frame bias must be applied before use. @duncaneddy (#413)
  • DNumericalOrbitPropagatorBuilder is now a runtime builder with required fields as builder() arguments, replacing the typestate (Set/Unset) builder; the entry point and setter names are otherwise unchanged (breaking, Rust only). @duncaneddy (#414)
  • DNumericalOrbitPropagator::new and DNumericalPropagator::new validate initial_covariance dimensions against the state dimension and return an error on mismatch. @duncaneddy (#414)
  • Reworked the Dawn-at-Ceres example to resolve Ceres via SBDB, load a Ceres SPK from Horizons, and model Sun and Jupiter third-body plus solar radiation pressure perturbations around the custom-defined body. @duncaneddy (#416)
  • Refactored the SPK reader behind a segment abstraction so a resolved chain can mix segment types, combining a type-21 small-body segment with the existing Chebyshev (types 2 and 3) segments from the DE kernels. @duncaneddy (#416)
  • Crates.io and PyPI package-validation CI jobs now run in parallel with the Rust and Python test suites instead of waiting for them to finish. @duncaneddy (#417)
  • Getting-started documentation now links function and class references to their corresponding library API and Learn pages. @duncaneddy (#417)

Removed

  • Breaking: SPKKernel — use SPICEKernel. @duncaneddy (#376)
  • Breaking: datasets::download_de_kernel — use datasets::naif::download_spice_kernel, which accepts any known kernel name (DE, satellite ephemeris, binary PCK). @duncaneddy (#376)
  • Breaking: set_global_almanac, get_loaded_kernel_type, and brahe_epoch_to_anise (ANISE types no longer appear in the public API). @duncaneddy (#376)
  • Breaking: ANISE as a runtime dependency (now dev-only). Downstream users relying on brahe's transitive anise dependency must add it directly. @duncaneddy (#376)
  • GravityModelType::EGM2008_360 (renamed to EGM2008_120; not deprecated — the variant was removed outright since the underlying packaged file changed). @duncaneddy (#397)
  • Internal fixed-size (degree ≤ 4) tidal coefficient evaluator (TideCoefficients, accel_low_degree_harmonics), superseded by the dynamically sized TideDeltas accumulator evaluated through the shared Clenshaw kernel. @duncaneddy (#397)
  • Removed the Starlink visualization example (docs/examples/visualizing_starlink.md, examples/examples/visualizing_starlink.py) and its committed figures; the GPS visualization example covers the same workflow. @duncaneddy (#409)

Fixed

  • ICGEM GFC parser now handles the gravity_constant header key and Fortran D-exponent notation, unblocking all non-Earth gravity model downloads (e.g. lunar GRGM660PRIM, Mars GMM-2B). @duncaneddy (#377)
  • Earth-centered propagation configured with the new third-body variants (Phobos, Deimos, Custom) routes through the SPK-backed path instead of panicking; validate() rejects LowPrecision ephemerides for bodies other than Sun/Moon and third bodies that coincide with the central body. @duncaneddy (#377)
  • Repo-wide cargo fmt and ruff lint/format debt that had accumulated since pre-commit hooks were never installed locally. @duncaneddy (#380)
  • crates/brahe-py clippy errors that were silently exempted from the pre-commit gate because the clippy hook lacked --workspace; the hook now covers the whole workspace. This included missing_safety_doc on 5 numpy-conversion methods in attitude.rs — on inspection none of them perform an actual unsafe operation (into_pyarray/reshape are safe in the pinned numpy version), so the vestigial unsafe keyword was removed instead of documenting a non-existent invariant — plus new_without_default/len_without_is_empty on the EOP provider bindings. @duncaneddy (#380)
  • Fix issue with some tests that rely on celestrak integration not being properly marked pytest.mark.integration. @duncaneddy (#385)
  • FLAGS declared past the tenth line of an example were silently ignored, because the runner parsed only the first ten lines. lunar_orbit.rs and mars_orbit.rs declare theirs on line 13, so both ran on every default just test-examples — downloading the GRGM660PRIM gravity model and the moon_pa_de440 kernel — despite being flagged, while their .py twins were correctly skipped. The runner now parses the whole leading comment block. @duncaneddy (#395)
  • Corrected the TCG secular drift from ~0.7 s/year to ~22 ms/year (IAU 2000 Resolution B1.9, L_G = 6.969290134e-10). @duncaneddy (#395)
  • Corrected the Epoch::time_system documentation, which listed five of the ten time systems and directed readers to a to_time_system() method that did not exist. @duncaneddy (#395)
  • Corrected typos in the TimeSystem rustdoc ("supposed" to "supported", "expeccted" to "expected"). @duncaneddy (#395)
  • Repaired six broken documentation links. The contributing guide, code of conduct, development guide, and license links omitted the /latest/ version segment and resolved to nothing. The development guide was additionally linked as developer_guidelines.html, a page that has never existed. Two versioning policy links used a directory-style URL where the site emits .html. @duncaneddy (#398)
  • brahe transform frame no longer emits duplicate output when the source and target frames are identical (missing early return). @duncaneddy (#407)
  • brahe transform coordinates now rejects non-ECI/ECEF values for --from-frame/--to-frame at validation instead of silently mis-routing them. @duncaneddy (#407)
  • Fixed 404s for all NETWORK-flagged example figures on the live documentation site (Doppler compensation, maximum communications gap, LRO, MRO, Dawn at Ceres, ground contacts, imaging opportunities, imaging data latency, tessellation). @duncaneddy (#409)
  • Fixed the NAIF kernel and 3D-texture example caches being stranded on PR merge refs, which forced every CI run into live downloads from naif.jpl.nasa.gov and Solar System Scope; caches now save from main only. @duncaneddy (#409)
  • Corrected the NASA NEN station count claim in the ground contacts example note. @duncaneddy (#409)
  • apply_proper_motion documentation now describes the IAU SOFA iauPmsafe space-motion transformation the function actually calls, rather than a hand-rolled tangent-plane method. @duncaneddy (#413)
  • Documentation figures for the lunar, Mars, Ceres, Earth-Moon free-return, and star-field examples now regenerate on every docs deploy instead of only via the weekly network run. @duncaneddy (#413)
  • Mismatched initial_covariance dimensions previously constructed successfully and panicked inside the first propagation step; construction now fails with a descriptive error, including from Python with extended (6+N) states. @duncaneddy (#414)
  • Captured output of the NETWORK-flagged getting-started examples (first script, Celestrak client) now reaches the deployed docs through a network-example-outputs cache, and the offline external-data examples emit output, so the affected pages no longer render empty output blocks. @duncaneddy (#417)
  • Star-field sensor view no longer flips direction at orbital plane crossings by using the orbit normal as a continuous roll reference, scrolls the star field along the elevation axis, and preserves per-star identity so markers fade in and out instead of jumping when the visible-star count changes. @duncaneddy (#417)
  • ci-success aggregation now includes the check-skip and delay-python gating jobs so a failed gate cannot be reported as a passing run. @duncaneddy (#417)

[1.6.2] - 2026-07-01

Added

  • Added native Linux arm64 wheel builds to the latest and release workflows, with platform-specific wheel artifact names to avoid artifact collisions. @hectcastro (#372)
  • CachingEOPProvider / CachingSpaceWeatherProvider (and initialize_eop() / initialize_sw()) now seed a missing cache from the compiled-in bundled data, so EOP and space-weather initialization succeed offline without an immediate network download. @duncaneddy (#374)
  • EOP and space-weather downloads now retry transient failures (connection errors, timeouts, HTTP 429/5xx) with exponential backoff and jitter (up to 4 attempts). @duncaneddy (#374)

Changed

Fixed

  • Fix compilation issue due to transitive dependency breaking change in ureq/time crates. Pin time until issue is resolved. @duncaneddy (#373)
  • Fixed issue with failing tests due to inability to update outdated EOP products. @duncaneddy (#373)
  • initialize_eop() / initialize_sw() no longer fail on a fresh/empty cache when the remote EOP or space-weather server is transiently unreachable (e.g. Standard EOP download request failed: io: Connection refused in CI); bundled data is used instead. @duncaneddy (#374)

[1.6.1] - 2026-06-16

Added

  • Added "Getting Started" section to main documentation page. @duncaneddy (#356)
  • integration flag to rust and python test suites, replacing ci. @duncaneddy (#365)
  • par_propagate_to (Python) now accepts a list that mixes KeplerianPropagator, SGPPropagator, and NumericalOrbitPropagator instances. Propagators are grouped by type and each group is propagated in parallel; results are written back to the original objects in place, preserving list order. @duncaneddy (#366)
  • MANUAL example flag for examples that must never run automatically (scaffolding templates and credential-gated examples), so the --ignore live-network run no longer sweeps them in. @duncaneddy (#366)

Changed

  • Update C04 data source to point to currently updating data series. @duncaneddy (#356)
  • Makes the plots submodule of the python package an optional dependency installed with brahe[plots] and not installed by default to avoid importing the heavy dependencies by default. @duncaneddy (#360)
  • Optimized the serial spherical-harmonic gravity evaluation (GravityModel::compute_spherical_harmonics / compute_spherical_harmonics_with_workspace) by precomputing the V/W recurrence reciprocal coefficients and reading the recurrence and accumulation buffers through bounds-check-free column slices. The workspace path is ~1.7–1.8× faster at degree/order 20–80 (the range common to LEO propagation) and ~1.6× faster at 360×360, with results unchanged to within 1e-12 relative. Measured on a 10-core Apple M1 Max. @duncaneddy (#361)
  • Raised the ParallelMode::Auto parallelization threshold for spherical-harmonic gravity from degree 150 to 210. The faster serial path moved the serial/parallel break-even point, so Auto now stays serial until parallel evaluation is at least as fast. @duncaneddy (#361)
  • compute_spherical_harmonics, compute_spherical_harmonics_with_workspace, accel_gravity_spherical_harmonics, and accel_gravity_spherical_harmonics_with_workspace now take a parallel: ParallelMode argument; GravityConfiguration::SphericalHarmonic gained a parallel field (defaults to Auto). @duncaneddy (#361)
  • Updated CI workflow to only run integration tests weekly and at release time to reduce frequent occurrence of CI tests failing due to external integration test failures. @duncaneddy (#365)
  • Improved CI caching to large texture / basemap files to be updated with integration tests weekly and avoid redownload on most CI runs. @duncaneddy (#365)
  • Updated pyo3 dependency version. @duncaneddy (#365)
  • Updated uv.lock. @duncaneddy (#365)
  • Tagged TEMPLATE.{py,rs} and getting_started/clients_spacetrack.{py,rs} as MANUAL so they are excluded from all automated example runs. @duncaneddy (#366)
  • Raised the per-example TIMEOUT to 600s for the starlink_propagation.py examples, which propagate the full live Starlink constellation and exceeded the default 180s limit during the weekly/release live-network run. @duncaneddy (#366)

Removed

Fixed

  • CSSI space weather data loader has been fixed to use the observed f107 rather than the adjusted f107. @jackyarndley (#362)
  • The NRLMSISE-00 implementation has been updated to use the previous day observed f107 as is standard. @jackyarndley (#362)
  • Fixed race condition in examples CI tests where two could download the same Natural Earth basemap and corrupt the file for both. @duncaneddy (#365)
  • Fixed regression from lazy-impoting brahe.plots that caused static-analyzers (griffee) to fail path-traversal. This caused build-docs to fail and slipped through due to other test-failure noise. @duncaneddy (#365)
  • Fixed test-examples flakyness on external integration dependencies. Ignore testing integration-based examples by default, but exercise them weekly in the integration tests to ensure continued coverage. @duncaneddy (#365)
  • par_propagate_to (Python) no longer raises TypeError when given a list containing more than one propagator type; mixed-type lists are now propagated correctly. @duncaneddy (#366)
  • Fixed the release test-examples integration job failing on --ignore: IGNORE was an overloaded bucket that mixed live-network examples with templates, credential-gated, slow, and broken examples, so opting into the live-network set pulled in examples that could never pass there. @duncaneddy (#366)

[1.6.0] - 2026-06-05

Added

  • Add RKF78 / RKF78Integrator support for high-order adaptive integration in Rust and Python. @Mtrya (#339)
  • Add RKF78 tests, examples, and API/reference documentation. @Mtrya (#339)
  • Added GMAT baselines. @duncaneddy (#340)
  • Added Basilisk baselines. @duncaneddy (#340)
  • profiles/ subdirectory with short, standalone rust and python scripts for use with profiling tools. @duncaneddy (#341)
  • Added justfile commands to run profiling scripts. @duncaneddy (#341)
  • Add baselines against Nyx 2.4.0 / ANISE 0.10.1. @duncaneddy (#342)
  • Added datasets.icgem submodule to integrate with ICGEM gravity field model distribution network. Listing and downloading models uses the local brahe cache to minimize network traffic. @duncaneddy (#343)
  • Added brahe vs astrojax benchmarks. @duncaneddy (#344)
  • Add ci-success stage to enable gating CI auto-merge on completion. @duncaneddy (#350)

Changed

  • Extend numerical propagation integrator selection and comparison examples to include RKF78. @Mtrya (#339)
  • Split baselines into speed and accuracy tests. @duncaneddy (#340)
  • Added ICGEMModel(body, model_name) to GravityModelType enabling ICGEM models to be specified for numerical orbit propagators. @duncaneddy (#343)
  • Scaled back python-version test matrix on PRs to minimum supported python version. @duncaneddy (#345)
  • Removed anise default feature dependency to just pull in SPICE kernel features. @duncaneddy (#350)

Fixed

  • EulerAngleOrder semantics now match what brahe's documentation always described and what the comparative benchmark expects. Prior to this fix, attitude.euler_angle_to_quaternion disagreed with OreKit, GMAT, and Basilisk. The discrepancy was traced through to a convention mismatch between the per-order formulas (faithfully transcribed from Diebel 2006) and the aerospace rotation-sequence interpretation users (and the docs) assumed. @duncaneddy (#338)
  • Fixed issue with CHANGELOG parsing in PRs where a blank line after a section header would fail to validate. @duncaneddy (#338)
  • Fixed issue with Orekit access baseline not properly inserting leap seconds in accuracy computation leading to in accurate error. @duncaneddy (#340)
  • Fixed build breaking from conflicting nalgebra required dependencies. @duncaneddy (#350)
  • Regression in use of custom python-based access constraints where they would give an error at use. @nkgotcode @duncaneddy (#354)

[1.5.2] - 2026-05-18

Fixed

  • Fix issue with new release workflow CHANGELOG generation that would result in an error causing the release to fail. Release notes now must be manually generated and committed prior to running the release action. @duncaneddy (#334)

[1.5.1] - 2026-05-18

Added

  • DNumericalOrbitPropagator::builder() - typestate builder that names required fields and lets optional fields be set by name or omitted. build() is only callable once epoch, state, and force_config are all set, enforced via Set/Unset marker type parameters. @markusz (#321)
  • Added states_ecef and states_eme2000 to SGPPropagator. @duncaneddy (#331)
  • Added states_eme2000 to KeplerianPropagator. @duncaneddy (#331)
  • Added states_eci, states_ecef, states_gcrf, states_itrf, and states_eme2000 to NumericalOrbitPropagator. @duncaneddy (#331)
  • Added states to NumericalPropagator. @duncaneddy (#331)
  • New high-fidelity propagator benchmarks under benchmarks/comparative/tasks/propagation_tasks.py: numerical_rk4_grav5x5, numerical_rk4_grav20x20_sun_moon, numerical_rk4_grav80x80_full. Each runs RK4 over one LEO revolution with matched force-model and frame settings on both sides (OreKit loads brahe's EGM2008_360.gfc via ICGEMFormatReader, GCRF inertial frame, ITRF / IAU 2006/2000A body-fixed rotation, DE-440 third-body ephemerides, identical spacecraft mass / area / Cd / Cr). @duncaneddy (#332)
  • New function-level acceleration benchmarks under benchmarks/comparative/tasks/force_model_tasks.py for point-mass gravity, 20×20 and 80×80 spherical-harmonic gravity, and third-body Sun/Moon. These evaluate a single acceleration at a fixed state and epoch, isolating force-model code from integrator behaviour. @duncaneddy (#332)
  • "Force Model" section in docs/about/benchmarks.md documenting the new function-level comparisons. @duncaneddy (#332)
  • GravityModel::load_uncached(model) — explicit cold-load primitive for users who need deterministic memory or want to profile the parse path. @duncaneddy (#332)
  • GravityModel::compute_spherical_harmonics_with_workspace(...) and accel_gravity_spherical_harmonics_with_workspace(...) — variants that accept caller-supplied V/W work matrices, letting hot-path callers (the numerical propagator's dynamics closure, batch orbit-determination residual code) amortize the per-call DMatrix::zeros((n_max + 2)²) allocation. At 80×80 the saved allocation+memset is ~15 µs per call. @duncaneddy (#332)
  • clear_gravity_model_cache() — manual cache invalidation, useful after replacing a FromFile(path) source on disk or in tests. @duncaneddy (#332)
  • mimalloc as the global allocator for the brahe-py Python extension and the comparative-benchmark Rust binary. The brahe core library remains allocator-agnostic so downstream Rust consumers pick their own. @duncaneddy (#332)
  • Added SECONDS_PER_DAY constant. @duncaneddy (#333)

Changed

  • NumericalOrbitPropagator and NumericalPropagator now no longer store accelerations by default to save space and improve performance. @duncaneddy (#330)
  • The FD fallback for HermiteQuintic is removed. Previously, calling HermiteQuintic on a trajectory without stored accelerations would silently degrade to finite-difference acceleration estimation if 3+ points were available; now it errors. Combined with the earlier store_accelerations: false default flip, any code path that today uses HermiteQuintic either needs NumericalPropagationConfig::with_store_accelerations(true) (propagator path) or trajectory.enable_acceleration_storage() (direct path). @duncaneddy (#330)
  • Performance, numerical propagator (no behaviour change): the DNumericalOrbitPropagator hot path now caches the ECI→body-fixed rotation across integrator stages, shares one Arc<GravityModel> across propagators with the same gravity config (no more 60 ms .gfc reload per construction), borrows the state vector instead of cloning it on every stage call, returns the orbital derivative as a stack-allocated Vector6<f64> instead of allocating a fresh DVector, and reuses the spherical-harmonic V/W work matrices across stages. @duncaneddy (#332)
  • GravityModel::from_model_type is now process-wide cache-backed. Repeated calls for the same GravityModelType parse the .gfc file once (~60 ms cold), then return owned clones from the cached Arc (~1 ms each). The public signature is unchanged. Cache is documented as unbounded — see the # Caution block on the function for the growth caveat with many distinct FromFile(path) sources, and use clear_gravity_model_cache() as the escape hatch. @duncaneddy (#332)
  • bias_precession_nutation switched from iauXys06a (full IAU 2000A nutation, ~1300 terms) to iauXys00b (truncated IAU 2000B, ~77 terms). Per-call cost dropped from ~150 µs to ~2.3 µs; agreement with the full series is sub-milliarcsecond, no regressions in tests. @duncaneddy (#332)
  • Refreshed benchmark plots and docs. @duncaneddy (#332)
  • Changed how adding a flaot second time to an Epoch (epc + 42.5) processes the internal addition. Previously, it would loop of remaining nanoseconds and convert them into integer seconds. For large second values, this would result in many loops for a single step, meaning that time addition, a frequent code hot-path, would grow linearly in time. Now it is an O(1) operation. @duncaneddy (#333)

Fixed

  • Update urllib3 to mitigate security vulnerability. @duncaneddy (#326)
  • Some estimation plots used unicode sigma instead of $\sigma$. When latex is installed and defaults to pdfLaTeX compiler, this causes an error. Switch to $\sigma$ everywhere. @duncaneddy (#331)
  • DNumericalOrbitPropagator::propagate_to may hang with fixed-step RK4. At src/propagators/dnumerical_orbit_propagator.rs:2312 the restore-dt_next guard used > instead of >=. With target_epoch = epc + N * step_size constructed by repeated arithmetic, float drift could leave target_rel slightly larger than the integer multiple; Fixed by changing the comparison to >= so the restore fires whenever the integrator returned its preferred step. @duncaneddy (#332)
  • Workspace mismatch in benchmarks/comparative/implementations/rust/Cargo.toml: the crate predated the brahe repo-root workspace and would fail with "current package believes it's in a workspace when it's not". Added an empty [workspace] table so cargo treats the bench as its own workspace root. @duncaneddy (#332)

[1.5.0] - 2026-05-06

Added

  • Added accel_earth_zonal_gravity — a hand-rolled closed-form J2–J6 zonal acceleration that mirrors accel_gravity_spherical_harmonics with m = 0 but evaluates ~1.5–2x faster, with agreement to <3 km over a 24 h LEO propagation when both are driven in the same Earth-fixed frame. @markusz (#302)
  • Added GravityConfiguration::EarthZonal { degree } variant and supporting ZonalHarmonicsDegree enum (J2..J6) so the numerical propagator can use the fast zonal path directly. @markusz (#302)
  • Added FrameTransformationModel (variants FullEarthRotation, EarthRotationOnly) and a frame_transform field on ForceModelConfig. EarthRotationOnly skips precession, nutation, and polar motion for ~1.5x faster ECI↔ECEF rotations at the cost of ~0.07° pole-tilt accuracy; FullEarthRotation (default) preserves prior behavior. @markusz (#302)
  • Added J3_EARTH, J4_EARTH, J5_EARTH, and J6_EARTH constants derived from EGM2008. @markusz (#302)
  • Added Python bindings for accel_earth_zonal_gravity, ZonalHarmonicsDegree, FrameTransformationModel, GravityConfiguration.earth_zonal(...), and the new J3_EARTH–J6_EARTH constants. @markusz (#302)
  • Added coefficient-derivation tests verifying each J_n_EARTH constant matches -C_n,0 * sqrt(2n + 1) against the EGM2008 fully-normalized Stokes coefficients. @markusz (#302)

Changed

  • Refined J2_EARTH to be derived from the EGM2008 C_2,0 Stokes coefficient (1.0826261738522227e-3) instead of GGM05s. Downstream values change in the 5th significant digit. @markusz (#302)
  • Reused a single ECI→body-fixed rotation per dynamics step across gravity, NRLMSISE-00 density, and drag inside DNumericalOrbitPropagator rather than recomputing the rotation per force term. @markusz (#302)
  • Update package lockfile. @duncaneddy (#306)

Removed

  • Removed rust artifact caching from CI since existing rust environment already provides (working) caching. @duncaneddy (#315)

Fixed

  • Add missing CHANGELOG.md release entries. @duncaneddy (#306)
  • Fix error with release note generation step of release workflow. @duncaneddy (#306)
  • Fix issue with awk-based release-note generation step of release workflow not populating release note contents. @duncaneddy (#306)
  • Fixed Starlink examples and documentation prose that still referenced the removed bh.datasets.celestrak / bh::datasets::celestrak API. @Mtrya (#310)
  • Fix 3rd party changes PRs not generating changelog fragments. @duncaneddy (#317)

[1.4.2] - 2026-04-23

Fixed

  • Fix brahe-py Crate not using root Cargo.toml as single-source-of-truth for package version. #298
  • Corrected format_exponential exponent offset in TLE generation (#300). BSTAR and second time derivative values are no longer 10x too small.

[1.4.1] - 2026-04-21

Changed

  • Improve documentation around versioning practices. #282
  • Add deprecation section to changelog #284
  • Bump version for v1.4.1 release #295

Fixed

  • Fix a permissions issue that pervented CHANGELOG validation from running on fork PRs #293

[1.4.0] - 2026-04-17

Added

Changed

  • Refactored repository structure to use a Rust workspace to breakup the python module from the core rust module. This pure-rust package consumers to avoid having to build the cdylib, which is only needed for python module builds. #278
  • pyo3 and numpy are now optional dependencies, activated via the python feature flag. Pure-Rust consumers that do not need the Python bindings no longer compile these crates.
  • Split the repository into a Cargo workspace so the PyO3 bindings live in a dedicated brahe-py member crate at crates/brahe-py/. The published brahe crate now builds only an rlib, eliminating the unnecessary cdylib artifact previously produced for every pure-Rust consumer. Rust downstream (use brahe::*) and Python downstream (import brahe) are unchanged.

Fixed

  • Fixed issue that would cause tests and auto-merge to not run on dependabot PRs #278

[1.3.4] - 2026-04-15

Fixed

  • Fix failing celestrak queries by migrating from celestrak.com to celestrak.org #272

[1.3.3] - 2026-04-10

Added

  • Add JD_J2000 constant to python bindings #264

[1.3.2] - 2026-04-06

Changed

  • Updated JOSS manuscript to comply with latest guidelines #256

Fixed

  • Fixed issue with SGP4 propagators erroring during parallel propagation on satellite reentry. Addressed by adding a termination_error field to SGPPropagators which will be documented and stop propagation rather than erroring all propagation. Issued with documentation builds in CI failing due to dependency changes. Fixed by updating uv.lock #256

[1.3.1] - 2026-03-29

Added

  • Added retry logic and exponential back-off to Celestrak client Added process-based rate-limiting to Celestrak client #244

Changed

  • Set default celestrak client cache age to 2 hours. #244
  • Bump version for v1.3.1 #248

Fixed

  • Pointed mike documentation deployment to use properdocs.yml #244
  • Fix mike set-default invocation to use properdocs.yml config #246

[1.3.0] - 2026-03-29

Added

  • Added python constructors for CCSDS OPM and OMM file types #225
  • Added dedicated celestrak and spacetrack submodules to the brahe CLI for querying and interfacing with space track. Added to_dict() and to_json() methods for GPRecord python module to enable easy interoperability #227
  • Added additional python test coverage #228
  • Added estimation module with support for EKF, UKF, and BLS (batched least-squares) filters Added estimation plotting routines Added implementations of magnetic field models. In particular the IGRF and WMMHR models. Added from_unix_timestamp and unix_timestamp methods to support interoperability with native unix time systems Added sponsor logo #241

Changed

  • Added additional test coverage #225
  • Renamed MJD2000 constant to MJD_J2000 to align with JD_J2000 naming #231
  • Improved documentation code samples by auto-generating, capturing, and rendering script outputs so values are guaranteed to be kept up to date. #234
  • Migrated to properdocs from mkdocs for documentation due to instability in project. #241

Fixed

  • Fixed issue with anomaly conversion CLI not properly using use_degrees #225

[1.2.0] - 2026-03-21

Added

  • Added polygon tessellation for large area access computation #199
  • Added additional benchmarks and comparisons to OreKit in benchmarks/comparisons #209
  • Add support for CCSDS OEM, OMM, OPM (blue-book) and CDM (pinkbook) types. Methods exist to load, access, modify, and create these data objects. #214
  • Add full support for XML and JSON output across OEM, OPM, and OMM files. #222

Changed

  • Have Propagators set a UUID by default Have Locations set a UUID by default Migrated default-generated UUIDs to UUIDv7 #202
  • Removed release environment flag from github release workflows Only retain last 3 minor documentation verions to prevent unbounded growth of github pages deployments. #209
  • Cleaned up space-track and celestrak documentation to improve readability and understandability #214
  • Added coverage reporting for python bindings and module #218
  • Improved CCSDS module round trip test coverage Ensures that default OEM to Trajectory conversion returns a DOrbitTrajectory so that it can immediately be used with access computation. #222

Fixed

  • Reverted compilation regression due to dependabot update. Ignored packages to prevent future regressions #194
  • Skip running CI-related tests on fork-PRs so that tests pass. #204
  • Changes access module type annotations from List to Sequence so that LSPs don't erroneously report a warning that location_accesses and other functions might mutate the list. Fix issue where python bindings for access properties would return a copy of the underlying data so mutations would not persist. Introduce AccessPropertyView so that it is now properly possible to access, set, and modify access properties in python. #206
  • Fixed issue with Gabbard plots not generating properly #218
  • Fix coverage reporting #220

[1.1.4] - 2026-03-05

Fixed

  • Fix regression from dependabot update Included type annotations in package generation #188

[1.1.3] - 2026-03-04

Added

  • Added SubdivisionConfig a subfield of AccessSearchConfig to control how subdivisions are created #181

Changed

  • Migrated to PyO3 v0.28 Update Cargo dependencies #179
  • Removed time_step parameter from internal find_access_windows method. Configuration of stepping and subdivision is now unified as a property of AccessSearchConfig #181

Fixed

  • Fixed issue in dataframe creation from dependabot update. #179

[1.1.2] - 2026-02-14

Fixed

  • Fix spacetrack client erroring out when query returns a lot of results
  • Fix spacetrack cdm_public route having wrong parent stem #169

[1.1.1] - 2026-02-14

Added

  • Added ability to configure TrajectoryMode on SGP4Propagator for applications where memory-use management is key. #167

Changed

  • The JOSS paper submission used code examples with the older celestrak API. This PR updates the code examples to use the updated, stabilized API.
  • Slightly adjust language in paper to avoid silly page overflow #165
  • Changed default interpolator used by NumericalOrbitPropgator for event detection from linear to 3rd degree Hermite. #167

Fixed

  • Make .current_epoch() property a method, not property in python bindings.
  • Fixed issue with event detection system in NumericalOrbitPropagator where it not correctly process events co-located with the current time, and would sometimes miss events in between time steps. #167

[1.1.0] - 2026-02-10

Added

  • Added par_propagate_to_d method. #109
  • Add Github issue templates for bugs and feature requests #113
  • Add arXiv citation to README and documentation. #124
  • Modified python bindings to support access computation with numerical orbit propagators. #126
  • Added spacetrack client module, enabling direct interaction with space-track.org APIs #150
  • Added BSL-1.0 to allowed dependency license list
  • Added GCAT dataset interface #152
  • Added simplified CelestrakClient interface to bridge gap between new interface and old one. #157

Changed

  • Bump version for 1.0.1 release #106
  • Renamed par_propagate_to to par_propagate_to_s
  • Move SGPPropagator to D-Type event detection. #109
  • Update documentation to add JOSS paper submission badge. #113
  • Update JOSS workflow to export tex artifacts to support arXiv upload. #122
  • Moved all Brahe errors in python bindings from OSError to BraheError to make them more distinguishable and easy to handle. #126
  • Updated JOSS paper with new required sections. #137
  • Enable manual triggering of latest documentation build in CI for handling EOP-update edge cases for releases. #139
  • Overhauled celestrak client. The celestrak client now uses a common type and query structure shared with the spacetrack module. In particular, the GPRecord serves as the common representation of OMM elements returned by both sites. This enables greater interoperability between other library modules that can consume or act on GPRecord types. The celestrak module is now it's own, stand-alone, module independent of datasets.
  • Migrate developer workflow from make.py to just
  • Remove dependency on BTreeCursor nightly feature, allowing crate to compile with stable rust. #150
  • Adjusted CelestrakQuery python bindings to properly use properties instead of methods for specific dataset queries. Example: New: CelestrakQuery.gp vs Old: CelestrakQuery.gp() #157

Fixed

  • Enabled event detection in par_propagate_to method in Python #109
  • Fixed broken documentation links in readme. (Thank you Stuart Bartlett for pointing it out) #129
  • Fix broken links in main documentation landing page. #131
  • Fix provider names for SSC stations in NASA Near Earth Network #143
  • Fix https://duncaneddy.github.io/brahe/ to redirect to the current default root URL. #147
  • Fixed tables in ephemeris documentation not being justified. #152
  • Reverted pyo3 version update due to incompatibility with numpy depdency
  • Prevented dependabot from auto-updating pyo3 #157
  • Properly pass environment secrets from the release workflow to child-workflows #159

[1.0.1] - 2025-12-04

Added

  • Added from_omm_elements to SGPPropagator. Supports direct initialization of propagator from OMM elements without needing to go through TLE lines. This is both more accurate as well as more robust to TLE ID depletion. #102
  • Add support for event detection to SGPPropagator
  • Add AOIEntryEvent and AOIExitEvent as premade event detectors. #104

Fixed

  • Fix typo in JOSS paper title #99

[1.0.0] - 2025-11-29

Added

  • {D}NumericalOrbitPropagator and python bindings
  • {D}NumericalPropagator and python bindings
  • Event detection system for numerical propagators to find events during propagation #89
  • Mean to osculating, osculating to mean orbital element transformations
  • Update OrbitStateProviders to provide state(s)_koe_mean and state(s)_koe_osc
  • Implement ECI<>ROE direct transformations
  • Add ephemeris_age to SGPPropagator
  • Implement Largrange, cubic Hermite, and quintic Hermite interpolation methods.
  • Add WalkerConstellationGenerator which enables rapidly generating propagators with walker-geometry configurations. #96

Changed

  • Make access computation use D-type propagators.
  • Standardize on
  • Add support for D-type returns to KeplerianPropagator and SGPPropagator
  • Standardized trait traits for {S|D}StateProvider, {S|D}CovarianceProvider, {S|D}StatePropagator
  • DTrajectory and DOrbitTrajectory can now store trajectories of arbitrary length
  • DTrajectory and DOrbitTrajectory can now store stm and sensitivity matricies alongside state and covariance.
  • Improved test coverage across modules
  • Changed how third-body ephemeris sources are defined and loaded. DE source is now a parameter instead of set by function name. Dynamically load BSP files when called.
  • Changed how Gravity models are defined in terms of enumeration types.
  • Renamed state_cartesian_to_osculating and state_osculating_to_cartesian as state_eci_to_koe and state_koe_to_eci #89
  • Improve trajectory module test coverage
  • Improve events module test coverage
  • Changed CI release workflow to publish documentation updates only after packages have been successfully published #96

Fixed

  • Numerical integration step and step_with_varmat now take explicit parameter values. #89

Removed

  • Removed STrajectory6 from python bindings. #89

[0.4.0] - 2025-11-28

Added

  • Add space weather data management and access modeled on Earth orientation data provider design. #73
  • Added support for control inputs to numerical integration
  • Added support for integration of sensitivity matrices to numerical integration functions
  • Added support for analytical and numerical computation of sensitivity matrix
  • Added documentation for integration with control inputs and sensitivity matrix integration
  • Added documentation for computation and handling of sensitivity matrices #78
  • Add JOSS draft paper #80
  • Added NRLMSISE-00 atmospheric density model implementation #82

Changed

  • Consolidates internal implementation of numerical integration
  • Added additional numerical integration tests to improve coverage
  • Consolidates duplicate type definitions across integrator files #78
  • Refactor celestrak.rs and naif.rs modules to use HttpClient structure to enable mocking and mock testing through mockall to make struct calls
  • Refactored main package documentation #84

Fixed

  • Auto-merge of bundled data update wasn't auto-merging #75
  • Removed erroring cache-check in python test workflow #78

[0.3.0] - 2025-11-18

Added

  • Added Integrators submodule with complete rust implementation, python bindings, documentation, and examples
  • Support for RK4, RKF45, DP54, and RKN1210 numerical integration methods
  • License validation and compliance checks. Package is now automatically checked to ensure all dependencies have permissive, commercially-adoptable licenses #63
  • NAIF Development Ephemeride dataset download and caching
  • High-accuracy DE440s-based ephemeris prediction
  • High-accuracy DE440s-based third-body acceleration prediction
  • Python bindings for orbit dynamics functions #68
  • Added geo_sma function to directly return the semi-major axis needed for a geostationary orbit. #69

Changed

  • Moved mathematics capabilities from utils submodule to dedicated math submodule.
  • Bumped version to 0.2.0 for release
  • Removed Rust test sections from coverage reporting #63
  • Orbit dynamics functions which previously required a position-only vector can now accept either a position-only or a state vector. Conversion will be handled by the IntoPosition trait. #68

Fixed

  • Fix inconsistencies in Python API reference header levels
  • Missing Ground station
  • Miscellaneous documentation improvements and fixes #63

[0.2.0] - 2025-11-17

Added

  • Added relative_motion submodule to contain functionality related to relative motion
  • Implemented RTN rotation and state transformations: rotation_eci_to_rtn, rotation_rtn_to_eci, state_eci_to_rtn, and state_rtn_to_eci
  • Implemented Relative Orbital Element (ROE) state transformations state_oe_to_roe and state_roe_to_oe
  • Added util::math functions sqrtm and spd_sqrtm to calculate matrix square root
  • Added util::math functions oe_to_radians and oe_to_degrees to reduce duplication in converting angle values in orbital element calcualtions #57
  • Implement CovarianceProvider trait for OrbitTrajectory. Provides covariance, covariance_eci, covariance_gcrf, and covariance_rtn
  • Add interpolation submodule to store consistent covariance interpolation methods
  • Implement covariance rotation from ECI to RTN frame
  • Extends OrbitTrajectory to optionally store covariance information. #59

Changed

  • Don't run PR tests on release CHANGELOG update. #49
  • Unit test and PR test workflows now cancel in-progress runs if a new commit lands
  • Added concurrency guards to auto-merge workflows. #53
  • Moved where internal vector/matrix (e.g. SMatrix3, SVector6) type aliases are defined from coordinates submodule to utils #57

Fixed

  • Don't trigger multiple changelog merges on changelog a PR. #49
  • Only trigger auto-merge on opened due to changelog PRs not issuing labeled events #51
  • Added additional triggers to auto-merge workflows to ensure they are properly triggered. #53
  • Fixed assorted typos and errors in docstrings related to covariance interpolation features. #61
  • Fixes package-data update CI workflow by moving to auto-PR, auto-merge approach #64

[0.1.3] - 2025-11-12

Added

  • Added python missing bindings for states_icrf and states_gcrf for KeplerianPropagator and SGPPropagator
  • Added additional tests across various module to improve test coverage. #36

Changed

  • Refactor frames.rs file into submodule with subfiles for long-term maintainability. #14
  • Automatically create and merge PRs for changelog updates #16
  • Auto-merge changelog PRs
  • Auto-merge dependabot PRs
  • Expand dependabot to cover python and rust packages #26
  • Bump package version to v0.1.3 #34
  • Skip unit test suite on auto-generated changelog PRs. #44

Fixed

  • PR changelogs were not being incorporated into the package changelog due to main-branch protection #16
  • Stop generation changelog PRs for auto-generated changelog PRs #26
  • Fixed issue with release pipeline release note generation #38
  • Fix auto-merge for changelog PRs by using PAT #40
  • Fix auto-merge workflow to accept PAT owner as actor. #42
  • Fix workflow release step to use workflow PAT and declare base branch #46