Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 03 Jun 13:00
· 205 commits to main since this release

This release adds three substantial capability areas on top of the 0.8.0 SGP4
substrate: a genuine three-axis strapdown INS, a loosely-coupled GNSS/INS
error-state EKF with closed-loop feedback, real snapshot and solution-separation
(ARAIM-style) RAIM with HPL/VPL and a runnable integrity scenario, and the first
step of GNSS-format interoperability (RINEX-3 GPS ephemeris ingestion).

Added

  • RINEX 3 GPS navigation-message parser (src/rinex.rs). First step toward
    GNSS-format interoperability: parse_nav reads a RINEX 3.x navigation file and
    decodes each GPS (G) broadcast-ephemeris record — the eight-line SV/epoch +
    BROADCAST ORBIT block — into a RinexEphemeris of Keplerian elements and
    clock corrections, with field names and units per IS-GPS-200. Handles the
    Fortran D-exponent float format (parse_d) and fixed-width column layout;
    records for non-GPS systems are skipped, not rejected, so a mixed file still
    yields its GPS ephemerides. Four tests: D-exponent parsing (including blanks
    and errors), a full record decoded against known field values with a GPS
    semi-major-axis sanity check (√A² ≈ 26 560 km), non-GPS skipping, and the
    empty-file case.
  • GPS broadcast-ephemeris → ECEF position (src/rinex.rs).
    RinexEphemeris::sv_position_ecef(t_tow) evaluates the satellite's Earth-fixed
    position from the parsed ephemeris via the IS-GPS-200 §20.3.3.4.3.1 user
    algorithm: Newton solution of Kepler's equation for the eccentric anomaly, the
    second-harmonic argument-of-latitude / radius / inclination corrections, and the
    rotation into ECEF accounting for Earth rotation since the reference epoch (with
    the GPS μ and Ω̇ₑ mandated by the spec, and a week-rollover tk fold). Three
    tests: the geocentric radius stays in the GPS band (≈ 26 560 km), the Earth-fixed
    speed is ~3.9 km/s, and evaluating a full week away reproduces the same position.
  • GPS SV clock bias with the relativistic correction (src/rinex.rs).
    RinexEphemeris::sv_clock_bias_s(t_tow) evaluates the broadcast clock
    polynomial af0 + af1·Δt + af2·Δt² about Toc plus the relativistic
    eccentricity term F·e·√A·sin Ek (IS-GPS-200 §20.3.3.3.3.1). A new
    EpochUtc::gps_time_of_week converts the record's calendar epoch to GPS
    time-of-week via Julian-day arithmetic from the GPS epoch (1980-01-06), and the
    Kepler solve is shared with the position evaluation. Tests: GPS time-of-week for
    a Sunday/Tuesday/Saturday (week boundaries), and the clock bias being
    af0-dominated with a present, bounded relativistic term. The L1 group-delay
    TGD is exposed but deliberately not folded in. Honest scope: a Propagator
    source, Galileo/BeiDou/GLONASS, and SP3 remain next steps. (docs/CAPABILITY.md
    updated to match.)
  • User-runnable integrity scenario kind (scenarios/integrity-raim.toml).
    The RAIM availability capability is now reachable from the CLI/TOML like every
    other pack: kind = "integrity" parses an IntegrityScenario (user orbit, one
    or more GNSS constellations, elevation mask, and the (sigma, P_fa, P_md, AL_H, AL_V) integrity config), runs constellation_raim_availability, and emits the
    per-epoch HPL/VPL availability map as JSON plus a self-contained SVG —
    protection levels over time against the alert limits, with a green/red
    availability strip. kshana scenarios/integrity-raim.toml writes the JSON, the
    chart, and an HTML report. The bundled scenario (24-satellite Walker, 1 m
    dual-frequency ranging, APV-I limits) reports ~95 % availability; it documents
    that single-frequency RAIM does not meet the vertical APV-I limit on one
    constellation, which is why vertical guidance uses SBAS/dual-frequency/ARAIM.
    Tests cover the dispatch, the availability-map JSON, and the SVG.
  • Runnable RAIM availability over a constellation (src/raim.rs). The
    integrity module had no caller — constellation_raim_availability makes it a
    genuine end-to-end entry point: at each epoch on a time grid it propagates the
    visible satellites (the same SGP4/Keplerian Propagators the engine uses),
    computes the no-fault protection levels, and judges availability against the
    horizontal/vertical alert limits, returning a serde-serializable
    RaimAvailabilityReport (per-epoch n_visible/HPL/VPL/available plus the
    availability fraction). A RaimConfig bundles (sigma, P_fa, P_md, AL_H, AL_V)
    and the per-epoch raim_availability_epoch is exposed for callers that resolve
    their own geometry. Three tests: an epoch judged available under APV-I limits on
    a ten-satellite geometry, made unavailable by an impossibly tight limit, and
    None levels below five satellites; and an end-to-end run over a 24-satellite
    Walker constellation that yields a finite availability map and serializes. (Six
    satellites — the residual-RAIM redundancy floor — honestly do not meet APV-I
    even at 1 m ranging; APV-I availability needs the denser geometry the test uses.)
  • Stanford(-ESA) integrity diagram accumulator (src/raim.rs). The standard
    way to summarise an integrity monitor over many epochs: it plots actual
    position error (x) against protection level (y) and classifies each epoch, by
    the diagonal y = x and the alert limit, into Available (PL bounds error,
    within AL), SystemUnavailable (PL bounds error but exceeds AL — safe,
    unusable), MisleadingInformation (PL < error ≤ AL), or
    HazardouslyMisleadingInformation (PL < error and error > AL — the unsafe
    failure). classify_stanford is the pure classifier; StanfordDiagram
    accumulates (error, PL) points against a fixed alert limit, exposing region
    counts, availability, integrity-event totals, and serde-serializable points
    for plotting/JSON export. Four tests: every region (including the error == PL
    bounded boundary), count/availability accumulation, and JSON round-trip. This
    is the reporting surface for the RAIM protection levels; wiring it into the
    constellation scenario and validating against a public dataset remain roadmap
    items.
  • Solution-separation (ARAIM-style) RAIM (src/raim.rs). A
    multiple-hypothesis integrity monitor alongside the existing residual/parity
    chi-squared snapshot_raim. For the all-in-view least-squares solution and
    every single-satellite exclusion sub-solution, it forms the separation
    Δ_k = x_k − x₀ — zero-mean Gaussian under no fault with covariance
    Cov(x_k) − Cov(x₀) (the nested-estimator identity, valid because the
    all-in-view solution is BLUE) — and so it both detects a fault and
    identifies the faulted satellite (the one whose exclusion gives the largest
    normalized separation). Horizontal/vertical protection levels follow the
    standard MHSS allocation PL = max(K_md·σ₀, max_k[K_fa·σ_ss,k + K_md·σ_k]),
    with K_fa = Φ⁻¹(1−P_fa/2), K_md = Φ⁻¹(1−P_md). New dependency-free
    normal_cdf/normal_quantile built from the module's existing regularized
    incomplete gamma (erf(x) = P(½,x²)). Four hand-derived tests: normal CDF /
    quantile against textbook values (Φ(1.95996)=0.975, the 1e-7 tail = 5.1993,
    symmetry); a fault-free geometry that does not alarm and yields finite, positive
    HPL/VPL; a 60-σ single-satellite bias that is detected and correctly
    identified (excluded_sv == 2); and the six-satellite redundancy floor. Closes
    the audit's "tautological integrity — no real RAIM/HPL/VPL" P0 gap on the
    algorithm side; gLAB-dataset validation and the Stanford-diagram accumulator
    remain roadmap items.
  • Closed-loop GNSS/INS integration (src/fusion/closed_loop.rs).
    ClosedLoopInsGnss wires the error-state EKF kernel to the three-axis strapdown
    mechanization: each IMU sample is corrected by the running bias estimates,
    mechanized forward, and the EKF covariance time-propagated with the matching
    navigation context; each GNSS position/velocity fix forms the INS−GNSS
    innovation and feeds the estimated position, velocity, attitude error (ψ, as a
    quaternion rotation) and accelerometer/gyro biases
    back into the solution,
    resetting the error-state mean. Feeding the attitude back (not only the biases)
    is required for stability — the tilt and accelerometer bias are a coupled pair,
    so correcting one without the other diverges. INS and GNSS are compared in a
    local tangent-plane NED frame using the mechanization's own radii of curvature
    (new mechanization::radii_of_curvature; NavState::omega_ie_n/omega_en_n
    exposed). This is the honest replacement for the hybrid pack's truth-snap
    reset
    . Three tests: a closed loop nulling an injected 8 m / −5 m position error
    to <0.1 m; an aided solution staying metre-bounded (<6 m) on a driving
    trajectory while a free-running INS diverges past 100 m; and the milestone
    benchmark — the fused solution's Monte-Carlo position RMS over a 60 s GNSS
    outage beats an unaided open-loop dead-reckoner by >2× (≈4× across seeds)
    .
    Honest limitation documented in the module: in loosely-coupled mode the accel
    bias and tilt are only weakly separable (both couple through gravity), so the
    delivered value is the bounded, corrected state and a clean outage-entry — not a
    precise inertial calibration; richer dynamics and the tightly-coupled extension
    remain roadmap items.
  • Loosely-coupled GNSS/INS error-state EKF kernel (src/fusion/gnss_ins_ekf.rs).
    A 15-state error-state extended Kalman filter — δx = [δp, δv, ψ, b_a, b_g]
    with the strapdown error dynamics from Groves 2013 §14.2 (specific-force/tilt
    coupling, Coriolis, body→nav bias projection, Gauss–Markov bias models), a
    first-order discrete transition Φ = I + F·dt, and a loosely-coupled
    position+velocity measurement update (H = [I₃ 0 0 0 0; 0 I₃ 0 0 0]) in Joseph
    form. Dependency-free dense linear algebra (Gauss–Jordan inverse, Joseph
    covariance update). A tight_coupling cargo feature gates a documented,
    not-yet-implemented pseudorange/Doppler update. 7 tests with hand-derived
    expectations: the skew/cross-product identity, a verified 3×3 inverse,
    covariance staying symmetric/PSD under prediction (and position uncertainty
    growing un-aided), a position fix shrinking the position covariance, exact
    recovery of a known position error at the analytic Kalman gain P/(P+R), and
    smaller corrections under larger measurement noise. This is the kernel that
    will replace the hybrid pack's open-loop truth-snap reset with closed-loop
    feedback (pack wiring + NaveGo validation to follow).
  • Deterministic IMU error model for the 3-axis strapdown navigator (src/inertial/imu_errors.rs).
    ImuErrorModel distorts a true body-frame (ω, f) pair into a measured one
    through five systematic categories (IEEE Std 952-1997 §A.2; Groves 2013 §4.3,
    Table 4.1): scale-factor (per-axis ppm gain error), misalignment /
    cross-coupling
    (off-diagonal triad non-orthogonality), g-sensitivity (a
    gyro rate bias proportional to specific force), quantization (rounding to
    the output LSB), and rate-ramp (a linear-in-time drift — the third Allan
    region), plus a constant turn-on bias. Every term defaults to zero, so
    ImuErrorModel::ideal() is a transparent pass-through and existing scenarios
    are unaffected. Each error source has an isolation test (scale linear to <0.01%,
    misalignment cross-axis above the VRW floor, g-sensitivity bias, LSB grid,
    linear ramp), and an end-to-end test drives a navigation error through the
    mechanization from a distorted IMU. Not modelled: vibration rectification error,
    temperature-gradient drift. (The shipped inertial scenario pack still runs the
    legacy 1-DOF scalar budget; this model feeds the 3-axis library.)
  • Coning and sculling compensation for the strapdown integrator. The
    attitude path adds the two-sample coning_increment (½ Δθ_prev × Δθ_cur); a
    coarse-rate (30 Hz, 5-samples/cycle) integration of a 5 Hz coning environment
    is verified to track fine-rate truth ≥ 3× better with the correction than naive
    increment-summing. The velocity path adds sculling_increment (½ Δθ × Δv,
    Groves eq. 5.82) and resolves the body velocity increment through a new
    NavState::step_increments increment-based update using the body-relative
    rotation Δθ_rel = Δθ_b − C_n^b ζ, so an Earth-fixed platform incurs no
    spurious sculling while a genuine vibration triggers the full term.
  • Full three-axis strapdown mechanization in the NED frame (src/inertial/mechanization.rs).
    NavState { q, v_ned, p_llh } is advanced by step(gyro_b, accel_f_b, dt) using
    the standard terrestrial-frame NED equations (Groves §5.4): body→NED attitude
    corrected for the inertial-to-nav rate ω_in = ω_ie + ω_en (Earth rotation +
    transport rate); specific force resolved body→NED through the DCM; velocity
    integrating v̇ = f_n − (2 ω_ie + ω_en) × v + g_n (Coriolis/transport + gravity);
    and geodetic position via the meridian/transverse radii of curvature. Gravity is
    the WGS-84 closed-form Somigliana normal (plumb-bob) gravity with a NIMA
    free-air altitude correction — never a hard-coded constant. This is the genuine
    three-axis navigator that supersedes the 1-DOF scalar error-budget path. Verified
    by physical invariants: a platform bolted to the rotating Earth at 45°N (sensing
    Earth rate + 1 g) stays within 1 mm over 60 s; a level north specific force gives
    v_N ≈ a·t and ½ a t² displacement; normal gravity matches the known
    equator/pole/45° surface values and the free-air lapse rate.
  • Three-axis attitude representation for strapdown INS (src/inertial/attitude.rs).
    A unit-quaternion Quaternion type (scalar-first, Hamilton convention) carrying
    body→nav rotation, with a DCM view (to_dcm/from_dcm via Shepperd's method),
    Hamilton product, axis-angle and rotation-vector (exact exp-map) constructors,
    and quaternion kinematics — both a first-order RK rate update (q̇ = ½ q ⊗ ω)
    and a coning-corrected rotation-vector update. The two-sample coning_increment
    (Savage / Bryan–Lewantowski, ½ Δθ_prev × Δθ_cur) supplies the rotation-rate
    cross-coupling term that scalar dead-reckoning omits. This is the attitude
    foundation for the full 3-axis mechanization that replaces the legacy 1-DOF
    error-budget path. Verified against closed-form rotations: constant-rate
    propagation matches the axis-angle quaternion to 1e-6, DCMs are orthonormal with
    unit determinant, and coning vanishes for single-axis motion. (src/inertial.rs
    is now the src/inertial/ module directory; the public path crate::inertial
    is unchanged.)
  • Geodetically-correct ground-station visibility (src/frames.rs).
    elevation, is_visible, and visible_count compute a satellite's elevation
    above a ground station's horizon against the WGS-84 ellipsoid normal (the
    true local vertical), not the geocentric radial — the two differ by up to the
    ~0.19° geodetic deflection, enough to flip near-horizon satellites in or out of
    an elevation mask. Verified end-to-end (a Walker constellation propagated,
    rotated TEME→ECEF, and counted from a geodetic site) and against the
    geocentric approximation.

Changed

  • CI reliability. The test-python-bindings job now builds the wheel with
    PyO3/maturin-action (manylinux container) instead of a raw host maturin build, eliminating an intermittent rustfmt-preview/cargo-fmt rustup
    conflict on the runner image. The deny job installs cargo-deny as a
    prebuilt binary via taiki-e/install-action instead of the Docker-based
    cargo-deny-action, removing a Docker Hub registry-pull timeout. Neither
    change affects the checks performed.

Full changelog: CHANGELOG.md · Docs: README