Skip to content

v0.16.0: Gauss-Jackson 8, frame overhaul, EGM96 default, tutorial reorg#79

Merged
ssmichael1 merged 7 commits intomainfrom
gauss_jackson
Apr 5, 2026
Merged

v0.16.0: Gauss-Jackson 8, frame overhaul, EGM96 default, tutorial reorg#79
ssmichael1 merged 7 commits intomainfrom
gauss_jackson

Conversation

@ssmichael1
Copy link
Copy Markdown
Owner

Summary

This PR delivers the 0.16.0 release: a large feature and cleanup branch built on top of the Gauss-Jackson 8 work. Major items:

Propagation

  • Gauss-Jackson 8 integrator — fixed-step 8th-order multistep predictor-corrector specialized for orbit propagation, with per-step dense output via quintic Hermite interpolation. Lives in a new satkit::orbitprop::ode submodule. Typically 3–10× fewer force evaluations than RKV98 for smooth long-duration propagation. Selected via Integrator::GaussJackson8 + a user-supplied gj_step_seconds.
  • Configurable max_stepsPropSettings::max_steps (Rust) / propsettings(max_steps=...) (Python). Applies uniformly to adaptive RK / Rosenbrock and to GJ8. Default 1_000_000, preserving the previous hard-coded GJ8 ceiling.
  • Precompute padding fixPropSettings::required_precompute_padding automatically extends interp-table bounds to cover the GJ8 backward startup stencil. Previously failed silently for gj_step_seconds > 60.

Coordinate Frames

  • Frame::RICFrame::RTN canonical rename (CCSDS OEM convention). Frame::RIC and Frame::RSW kept as compile-time aliases, so existing code using either name still compiles and Frame::RIC == Frame::RTN.
  • Frame::NTW (velocity-aligned) — natural for prograde/retrograde burns on eccentric orbits. Accepted by maneuvers, thrust, uncertainty, and frame transforms.
  • LVLH maneuvers and thrust — LVLH is now a supported maneuver / thrust coordinate frame (previously valid only for uncertainty).
  • Unified frame-transform APIframetransform::to_gcrf(frame, pos, vel) and from_gcrf(frame, pos, vel) replace the combinatorial per-frame helpers. state_to_gcrf / gcrf_to_state handle the position+velocity pair with the correct TIRS-frame Earth-rotation term for ITRF↔GCRF. Validated against Vallado Example 3-14.

Breaking changes

  • Unified uncertainty APISatState::set_pos_uncertainty(sigma, frame) and set_vel_uncertainty(sigma, frame) replace the four per-frame methods (set_lvlh_pos_uncertainty / set_lvlh_vel_uncertainty / etc.), which are removed. Each call preserves the 3×3 block it isn't updating, so pos-then-vel correctly builds a full 6×6 covariance — the old methods silently overwrote the whole matrix.
  • Python parity with Rustsatstate.add_maneuver, set_pos_uncertainty, set_vel_uncertainty, and thrust.constant now all require an explicit frame argument from Python. Added ergonomic helpers: add_prograde, add_retrograde, add_radial, add_normal.
  • Default gravity model is now EGM96 (was JGM3). Applies to PropSettings::default() and the standalone gravity() / gravity_and_partials() helpers. Sub-meter numerical difference for typical LEO propagation over a day, but the default selection changes.

Documentation

  • Tutorial renames: "Coordinate Frame Transforms" → "Coordinate Frames" (now primarily a description of the frames themselves, with an expanded TEME section), and "ITRF Coordinates" → "Geodetic Coordinates" (about the itrfcoord data type, not the frame). Nav reordered so Coordinate Frames comes before Geodetic Coordinates. Reciprocal cross-reference notes at the top of both.
  • Expanded TEME section with the "True Equator, Mean Equinox" etymology, the three practical awkwardness points, API table, and the Vallado 2006 reference.
  • "Why yet another time type?" section added to the Time Systems tutorial.
  • New guide: "Theory: Maneuver Coordinate Frames" (docs/guide/maneuver_frames.md) comparing GCRF / RTN / NTW / LVLH with the flight-path-angle derivation and a worked e=0.3 numeric example.
  • New API reference page docs/api/frame.md documenting the Frame enum with all aliases.
  • MathJax fix — removed the ignoreHtmlClass / processHtmlClass: "arithmatex" restriction that was causing MathJax to skip mkdocs-jupyter notebook HTML entirely. Equations in the Quaternions tutorial (and others) now render.

Tooling

  • release.yml check_version now verifies the tag against all three version strings (root Cargo.toml, python/Cargo.toml, pyproject.toml). Previously only the root Cargo.toml was checked, which allowed python/Cargo.toml to drift silently. That drift (0.15.1 → 0.16.0) was caught and fixed in this branch.

Test plan

  • cargo test --release — 157 lib + 41 doc-tests pass
  • pytest python/test/ — 81 Python tests pass (up from 71 at 0.15.1)
  • mkdocs build --strict — builds cleanly; renamed tutorials render; new api/frame.md renders
  • Version strings in sync: Cargo.toml, python/Cargo.toml, pyproject.toml all at 0.16.0
  • Vallado Example 3-14 single-call state transform test passes
  • test_gravity explicitly pinned to gravmodel.jgm3 (ICGEM reference values are JGM3-specific)
  • GitHub Actions CI green on this branch

🤖 Generated with Claude Code

ssmichael1 and others added 7 commits March 29, 2026 18:13
- TLE.from_url(url): fetches plain-text TLE data and parses it
- OMM.from_url(url): fetches OMM data with auto-detection of JSON vs XML
- Python omm_from_url(url): returns list of dicts compatible with sgp4()
- Full Python bindings, type stubs, and docstrings
- Update user guide with URL loading examples, remove requests dependency
  from OMM example
- Update API reference to include omm_from_url

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major additions on this branch:

* Gauss-Jackson 8 fixed-step multistep integrator for high-precision orbit
  propagation, with per-step dense output and quintic Hermite interpolation.
  Specialised for 2nd-order ODEs via a combined GJ + Summed-Adams
  formulation that handles velocity-dependent forces naturally. Typically
  3-10x fewer force evaluations than RKV98 for smooth long-duration
  propagation. Selected via Integrator::GaussJackson8 with a user-supplied
  gj_step_seconds. No STM support. Lives in the new
  satkit::orbitprop::ode submodule (integrator is astrodynamics-specific
  enough that it doesn't belong in numeris).

* Frame::NTW (velocity-aligned: T=v_hat, W=h_hat, N=T x W). Natural for
  prograde/retrograde burns on eccentric orbits: a pure +T delta-v of
  magnitude dv adds exactly dv to |v|, while a RIC/LVLH in-track burn of
  the same magnitude loses 10(1-cos gamma). Compile-time Frame::RSW and
  Frame::RTN aliases point at Frame::RIC since the axes are identical
  (just different community names).

* LVLH added as a supported maneuver/thrust coordinate frame (previously
  only valid for uncertainty).

* Unified uncertainty API: SatState::set_pos_uncertainty(sigma, frame)
  and set_vel_uncertainty(sigma, frame) replace the four per-frame
  methods. Supports GCRF, LVLH, RIC, NTW. Each call preserves the 3x3
  block it is not updating, so pos-then-vel builds a full 6x6 covariance
  (the old methods silently overwrote the whole matrix). Removed the old
  methods entirely.

* Python/Rust API parity on frame handling: add_maneuver,
  set_pos_uncertainty, set_vel_uncertainty, and thrust.constant now all
  require an explicit frame argument from Python, matching the Rust API
  (no silent defaults). Added ergonomic add_prograde / add_retrograde /
  add_radial / add_normal helpers on satstate alongside the generic
  add_maneuver.

* Ergonomic ImpulsiveManeuver constructors in Rust: prograde, retrograde,
  radial_out, normal, plus gcrf / ric / ntw for arbitrary-vector burns.

* Precompute bounds fix: Precomputed::new_padded takes an explicit
  padding, and PropSettings::required_precompute_padding automatically
  extends interp-table bounds to cover the GJ8 backward startup stencil
  (4 * gj_step_seconds on each end). Previously failed silently for
  gj_step_seconds > 60.

* New "Theory: Maneuver Coordinate Frames" guide
  (docs/guide/maneuver_frames.md) comparing GCRF/RIC/NTW/LVLH with the
  flight-path-angle derivation, a worked numeric example showing the
  0.245 m/s discrepancy on an e=0.3 orbit, a cheat sheet, and a summary
  table. Wired into mkdocs nav.

Other changes:

* Frame derives Copy + PartialEq + Eq (strictly permissive).
* PyFrame::NTW, PyIntegrator::gauss_jackson8, PyPropSettings::gj_step_seconds,
  and PyPropResult::gj_dense exposed via the Python bindings.
* .pyi stubs updated throughout: new frame variant, new integrator
  variant, gj_step_seconds, unified uncertainty API, ergonomic
  maneuver helpers, frame docstrings corrected re: covariance
  conventions.
* Covariance Propagation tutorial notebook simplified to use the unified
  API (dropped hand-rolled LVLH->GCRF rotation in favor of
  set_vel_uncertainty).
* satprop guide integrator table and covariance / maneuver examples
  updated.

Tests: 153 Rust + 29 Python propagation tests pass (up from 139 / 24
at branch start).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-on work on the gauss_jackson branch covering canonical-name
cleanup, a unified frame-transform API, tutorial overhauls, and the
default gravity model switch.

* Rename Frame::RIC to Frame::RTN as the canonical name (CCSDS OEM
  convention). Frame::RIC and Frame::RSW are kept as compile-time
  aliases, so Frame::RIC == Frame::RTN and existing code using either
  name still compiles. Python exposes frame.RIC and frame.RSW as
  class-level aliases of frame.RTN.

* Unified frame-transform API: frametransform::to_gcrf(frame, pos, vel)
  and from_gcrf(frame, pos, vel) replace the combinatorial per-frame
  helpers. state_to_gcrf / gcrf_to_state handle the position+velocity
  pair with the correct TIRS-frame Earth-rotation term (omega x r_tirs)
  for ITRF<->GCRF. Validated against Vallado Example 3-14 in a new
  single-call test.

* Default gravity model is now EGM96 (was JGM3). Applies to
  PropSettings::default() and the standalone gravity() /
  gravity_and_partials() Python helpers.

* Coordinate Frame Transforms tutorial rewrite: explicit GCRS/ICRF and
  ITRS definitions, geodetic-vs-geocentric explanation, cartopy ground
  track overlay, time-series qgcrf2itrf_approx-vs-full error plot over
  30 years. Dropped the low-value 24-hour Earth rotation section.

* New API reference page: docs/api/frame.md documenting the Frame enum
  with all variants and aliases. Wired into mkdocs nav and
  docs/api/index.md.

* MathJax now accepts both \(...\) / \[...\] and dollar delimiters so
  equations render correctly in Jupyter-notebook tutorials (previously
  broken in Quaternions.ipynb and others).

* Python pypropsettings/pygravity default changed to egm96; .pyi stubs
  and docstrings updated.

* Bump version to 0.16.0 (Cargo.toml, pyproject.toml) and add 0.16.0
  CHANGELOG entry covering the full branch (GJ8, NTW/LVLH maneuvers,
  unified uncertainty API, RTN rename, EGM96 default, docs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tutorial renames (nav + filenames):

* "Coordinate Frame Transforms" -> "Coordinate Frames". The tutorial is
  primarily a description of the frames themselves, not just the
  rotations between them.
* "ITRF Coordinates" -> "Geodetic Coordinates". This tutorial is about
  the itrfcoord data type (geodetic / Cartesian / ENU / NED / geodesic
  distance), not the ITRF reference frame. The old name made it sound
  like two views of the same topic as Coordinate Frames.
* mkdocs.yml nav reordered so Coordinate Frames (frame theory) comes
  before Geodetic Coordinates (data type built on top).
* docs/tutorials/index.md updated with new titles and descriptions.
* Added reciprocal cross-reference notes at the top of both tutorials
  so readers arriving at either one know where the other topic lives.

Content expansions:

* Coordinate Frames: expanded TEME section with the origin of the
  "True Equator, Mean Equinox" name (intentional half-and-half
  construction), the three practical awkwardness points (not uniquely
  defined, time-dependent orientation, positions cannot be compared
  directly), the API table, and the Vallado 2006 reference.
* Coordinate Frames: added an explicit list of the intermediate and
  satellite-local frames (CIRS/TIRS, EME2000/ICRF, RTN/NTW/LVLH) with
  pointers to the maneuver frames guide.
* Time Systems: added a "Why yet another time type?" section at the
  top covering time-scale-as-first-class, high-precision internal
  representation, correct leap-second handling, UT1/TDB with no extra
  dependencies, and the single-type-across-Rust-and-Python story.

MathJax fix:

* docs/javascripts/mathjax.js: remove the
  `ignoreHtmlClass: ".*|"` + `processHtmlClass: "arithmatex"`
  restriction that caused MathJax to skip notebook HTML entirely
  (mkdocs-jupyter does not wrap notebook-cell math in an .arithmatex
  span). Equations in the Quaternions notebook and all other tutorials
  now render. Plain markdown pages still work because
  pymdownx.arithmatex (generic mode) emits raw delimiters that MathJax
  picks up under default scanning.

Test fix:

* python/test/test_frames.py::TestGravity::test_gravity now explicitly
  passes `model=sk.gravmodel.jgm3`. The ICGEM reference values in that
  test are for JGM3 specifically; previously they relied on the
  default, which switched to EGM96 in 0.16.0. Reference-value tests
  should always name the model they compare against.

All 20 notebooks re-executed, verified to run cleanly, and stripped of
outputs (mkdocs-jupyter re-executes at build time). 81 Python tests
pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* PropSettings.max_steps (Rust) / propsettings(max_steps=...) (Python):
  user-configurable maximum integrator step count before the propagator
  aborts with a max-steps error. Applies uniformly to adaptive RK /
  Rosenbrock (via numeris::ode::AdaptiveSettings::max_steps) and to
  Gauss-Jackson 8 (via its own settings). Default 1_000_000, which
  preserves the previous hard-coded GJ8 ceiling and is a loosening of
  the previously inherited numeris RK default of 100_000. Getter/setter
  and .pyi stubs (constructor kwarg, @Property pair, docstrings, default
  list).

* release.yml check_version: verify the git tag against Cargo.toml,
  python/Cargo.toml, AND pyproject.toml — not just Cargo.toml as before.
  Previously python/Cargo.toml could drift silently if a version bump
  was applied by hand instead of through cargo release; that drift
  (0.15.1 -> 0.16.0) was caught and fixed in this branch. Any future
  drift hard-blocks the release workflow.

* python/Cargo.toml version synced to 0.16.0 to match Cargo.toml /
  pyproject.toml. verified via cargo metadata cross-check.

* README.md quick-start: gravity_model=sk.gravmodel.egm96 with a
  comment annotating the default. Previously showed jgm3 which stopped
  being the default in 0.16.0.

* docs/guide/satprop.md:
  - new "Integrator step budget: max_steps" admonition under the
    integrator selection section
  - gravity model table: egm96 is now labelled (default), jgm3 demoted

* docs/tutorials/High Precision Propagation.ipynb: fix stale
  "jgm3 (default)" comment in the settings cell, flip to egm96.

* CHANGELOG: new sub-sections for the max_steps feature and the release
  tooling hardening, added to the existing 0.16.0 entry. Explicitly
  note the default = 1_000_000 rationale (matches old GJ8, loosens RK).

* Display for PropSettings includes Max Steps line.

157 Rust + 41 doc-tests + 81 Python tests pass. All three version
strings (Cargo.toml, python/Cargo.toml, pyproject.toml) verified at
0.16.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ssmichael1 ssmichael1 merged commit b896848 into main Apr 5, 2026
8 checks passed
@ssmichael1 ssmichael1 deleted the gauss_jackson branch April 5, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant