satkit 0.21.1

Satellite Toolkit
Documentation
//! Errors produced by the `orbitprop` module.

use numeris::ode;
use thiserror::Error;

use crate::Frame;
use crate::Instant;

/// Errors that can occur while configuring or executing orbit propagation.
#[derive(Debug, Error)]
pub enum Error {
    // -- propagator-internal errors --------------------------------------
    /// Returned when the integrated state matrix has an unexpected
    /// number of columns.
    #[error("Invalid number of columns: {c}")]
    InvalidStateColumns { c: usize },

    /// Returned by the dense-output interp helpers when the underlying
    /// ODE solution does not carry interpolation data.
    #[error("No Dense Output in Solution")]
    NoDenseOutputInSolution,

    /// Wraps an [`ode::OdeError`] surfaced by the chosen integrator.
    /// `OdeError` does not implement `std::error::Error` (numeris keeps
    /// it as a plain `Display`-only enum), so this variant is built
    /// manually rather than via `#[from]`.
    #[error("ODE Error: {0}")]
    OdeError(ode::OdeError),

    /// RODAS4 does not support state transition matrix propagation
    /// (`C == 7`).
    #[error("RODAS4 does not support state transition matrix propagation")]
    RODAS4NoSTM,

    /// Gauss-Jackson 8 does not support state transition matrix
    /// propagation (`C == 7`).
    #[error("Gauss-Jackson 8 does not support state transition matrix propagation")]
    GaussJackson8NoSTM,

    // -- precomputed.rs --------------------------------------------------
    /// Returned by the [`Precomputed`](crate::orbitprop::Precomputed)
    /// constructors when the interpolation step is zero, negative, or
    /// non-finite (a zero step would otherwise attempt a `usize::MAX`
    /// allocation; a negative one builds a table covering the wrong range).
    #[error("Precomputed interpolation step must be finite and > 0, got {step}")]
    InvalidPrecomputeStep { step: f64 },

    /// Returned by [`Precomputed::interp`](crate::orbitprop::Precomputed::interp)
    /// when the requested time falls outside the precomputed range.
    #[error("Precomputed::interp: time {time} is outside of precomputed range : {begin} to {end}")]
    PrecomputedOutOfRange {
        time: String,
        begin: String,
        end: String,
    },

    /// Wraps an error surfaced while building a
    /// [`Precomputed`](crate::orbitprop::Precomputed) interp table from
    /// JPL ephemeris data.
    #[error(transparent)]
    Jplephem(#[from] crate::jplephem::Error),

    // -- satstate.rs -----------------------------------------------------
    /// Returned by [`SatState::set_pos_uncertainty`](crate::orbitprop::SatState::set_pos_uncertainty),
    /// [`SatState::set_vel_uncertainty`](crate::orbitprop::SatState::set_vel_uncertainty),
    /// and the internal `cov_frame_to_gcrf` helper when the supplied
    /// frame is not one of the supported orbital or inertial frames.
    #[error("Unsupported frame for uncertainty: {frame}. Must be GCRF, LVLH, RIC, or NTW")]
    UnsupportedUncertaintyFrame { frame: Frame },

    /// Returned by [`SatState::propagate`](crate::orbitprop::SatState::propagate)
    /// when a scheduled maneuver uses a frame in which a delta-v cannot be
    /// resolved. Validated up front so it surfaces as an error rather than a
    /// panic inside the force evaluation.
    #[error("Unsupported frame for maneuver: {frame}. Must be GCRF, RTN (RIC), NTW, or LVLH")]
    UnsupportedManeuverFrame { frame: Frame },

    /// Returned by [`ContinuousThrust::new`](crate::orbitprop::ContinuousThrust::new)
    /// when the thrust frame is not one in which a thrust acceleration can be
    /// resolved. Validated at construction so it surfaces as an error rather
    /// than a panic inside the force evaluation.
    #[error("Unsupported frame for thrust: {frame}. Must be GCRF, RTN (RIC), NTW, or LVLH")]
    UnsupportedThrustFrame { frame: Frame },

    // -- settings.rs -----------------------------------------------------
    /// Returned by [`PropSettings::set_gravity`](crate::orbitprop::PropSettings::set_gravity)
    /// when `order > degree`.
    #[error("Gravity order ({order}) must be ≤ degree ({degree})")]
    InvalidGravityOrder { order: u16, degree: u16 },

    /// Returned when the requested gravity degree exceeds what the built-in
    /// coefficient tables and evaluator support
    /// ([`MAX_GRAVITY_DEGREE`](crate::earthgravity::MAX_GRAVITY_DEGREE)).
    /// Previously such requests were silently evaluated at the maximum.
    #[error("Gravity degree ({degree}) exceeds the maximum supported degree ({max})")]
    InvalidGravityDegree { degree: u16, max: u16 },

    /// Returned by [`Precomputed::new_padded`](crate::orbitprop::Precomputed::new_padded)
    /// when the padding is not a finite number.
    #[error("Precomputed table padding must be finite, got {padding}")]
    InvalidPrecomputePadding { padding: f64 },

    /// Returned by [`Precomputed::new_padded`](crate::orbitprop::Precomputed::new_padded)
    /// when the span / step combination would need more table entries than
    /// [`MAX_PRECOMPUTE_ENTRIES`](crate::orbitprop::MAX_PRECOMPUTE_ENTRIES)
    /// (≈1.3 GB). Use a larger step or a shorter span.
    #[error(
        "Precomputed table would need {entries} entries (max {max}); \
         use a larger interpolation step or a shorter propagation span"
    )]
    PrecomputeTooLarge { entries: u64, max: usize },

    /// Returned when a Gauss-Jackson 8 propagation is requested over a span
    /// shorter than its 8-step startup requires. Reduce `gj_step_seconds` or
    /// use an adaptive integrator (e.g. RKV98) for short arcs.
    #[error(
        "Propagation span ({span} s) is too short for Gauss-Jackson 8, which \
         needs at least 8 steps ({min} s at the configured step). Reduce \
         gj_step_seconds or use an adaptive integrator."
    )]
    GJIntervalTooShort { span: f64, min: f64 },

    // -- EOP coverage (precomputed.rs / propagator.rs) ---------------------
    /// No Earth Orientation Parameters table is loaded (file missing and
    /// download failed, or an empty table installed). A propagation would
    /// then run with zero polar motion / UT1−UTC / nutation corrections and
    /// be silently wrong by metres, so it is refused. Run
    /// `satkit::utils::update_datafiles()` or point `SATKIT_DATA` at a
    /// directory containing `EOP-All.csv`.
    #[error(
        "no Earth Orientation Parameters (EOP) table is loaded; run \
         satkit::utils::update_datafiles() or set SATKIT_DATA to a directory \
         containing EOP-All.csv"
    )]
    EopUnavailable,

    /// Returned by [`propagate`](crate::orbitprop::propagate) when
    /// [`PropSettings::require_eop_coverage`](crate::orbitprop::PropSettings::require_eop_coverage)
    /// is set and the propagation span (plus integrator padding) extends
    /// past the end of the loaded EOP table. Without the flag the last EOP
    /// row is held constant and a one-time warning is printed instead.
    #[error(
        "propagation span extends to {span_end} but EOP data ends at {table_end} \
         (PropSettings::require_eop_coverage is set); refresh the data files with \
         satkit::utils::update_datafiles() or clear the flag to extrapolate"
    )]
    EopCoverage {
        span_end: Instant,
        table_end: Instant,
    },

    /// Wraps a frame-transform error — in practice a missing or unreadable
    /// IERS precession-nutation table detected by
    /// [`ierstable::preload`](crate::frametransform::ierstable::preload)
    /// while building a [`Precomputed`](crate::orbitprop::Precomputed) table.
    #[error(transparent)]
    FrameTransform(#[from] crate::frametransform::Error),
}

impl From<ode::OdeError> for Error {
    fn from(e: ode::OdeError) -> Self {
        Self::OdeError(e)
    }
}

/// Convenient type alias used throughout the `orbitprop` module.
pub type Result<T> = std::result::Result<T, Error>;