use std::ffi::CStr;
use crate::observers::obs_code_from_bytes;
use crate::orbit::Orbit;
use crate::propagate::{CovarianceKind, ForceModelTier, PropagatedState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RejectionReason {
Accepted,
ChiSquared,
SigmaClip,
CooksDistance,
Adaptive,
UnsupportedObservatory,
CMC2003,
RadarObservationsUnsupported,
OccultationObservationsUnsupported,
OutsideArc,
NotEvaluated,
}
impl RejectionReason {
pub(super) fn from_int(v: i32) -> Self {
match v {
0 => RejectionReason::Accepted,
1 => RejectionReason::ChiSquared,
2 => RejectionReason::SigmaClip,
3 => RejectionReason::CooksDistance,
4 => RejectionReason::Adaptive,
5 => RejectionReason::UnsupportedObservatory,
6 => RejectionReason::CMC2003,
7 => RejectionReason::RadarObservationsUnsupported,
8 => RejectionReason::OccultationObservationsUnsupported,
9 => RejectionReason::OutsideArc,
_ => RejectionReason::NotEvaluated,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObservationResidual {
pub obs_id: String,
pub obs_code: String,
pub ast_cat: Option<String>,
pub epoch: crate::Epoch,
pub ra_residual_arcsec: f64,
pub dec_residual_arcsec: f64,
pub chi2: f64,
pub dof: u32,
pub probability: f64,
pub selected: bool,
pub residual_cov_ra: f64,
pub residual_cov_dec: f64,
pub residual_cov_corr: f64,
pub rejection_reason: RejectionReason,
pub rejection_criterion: f64,
pub rejection_threshold: f64,
pub rejection_effective_threshold: f64,
pub rejection_information_loss: f64,
pub cooks_distance: f64,
pub leverage: f64,
pub fractional_information: f64,
pub along_track_arcsec: f64,
pub cross_track_arcsec: f64,
pub along_track_error_arcsec: f64,
pub cross_track_error_arcsec: f64,
pub track_position_angle_deg: f64,
}
impl ObservationResidual {
pub(super) fn from_ffi(r: &empyrean_sys::EmpyreanObservationResult) -> Self {
let obs_id = if r.obs_id.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(r.obs_id) }
.to_string_lossy()
.into_owned()
};
let ast_cat = if r.ast_cat.is_null() {
None
} else {
let s = unsafe { CStr::from_ptr(r.ast_cat) }
.to_string_lossy()
.into_owned();
(!s.is_empty()).then_some(s)
};
Self {
obs_id,
obs_code: obs_code_from_bytes(&r.obs_code),
ast_cat,
epoch: crate::Epoch::from_mjd_tdb(r.epoch_mjd_tdb),
ra_residual_arcsec: r.ra_residual_arcsec,
dec_residual_arcsec: r.dec_residual_arcsec,
chi2: r.chi2,
dof: r.dof,
probability: r.probability,
selected: r.selected != 0,
residual_cov_ra: r.residual_cov_ra,
residual_cov_dec: r.residual_cov_dec,
residual_cov_corr: r.residual_cov_corr,
rejection_reason: RejectionReason::from_int(r.rejection_reason),
rejection_criterion: r.rejection_criterion,
rejection_threshold: r.rejection_threshold,
rejection_effective_threshold: r.rejection_effective_threshold,
rejection_information_loss: r.rejection_information_loss,
cooks_distance: r.cooks_distance,
leverage: r.leverage,
fractional_information: r.fractional_information,
along_track_arcsec: r.along_track_arcsec,
cross_track_arcsec: r.cross_track_arcsec,
along_track_error_arcsec: r.along_track_error_arcsec,
cross_track_error_arcsec: r.cross_track_error_arcsec,
track_position_angle_deg: r.track_position_angle_deg,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ResidualSummary {
pub num_obs: usize,
pub num_selected: usize,
pub num_rejected: usize,
pub chi2: f64,
pub dof: usize,
pub reduced_chi2: f64,
pub rms_ra_arcsec: f64,
pub rms_dec_arcsec: f64,
pub rms_combined_arcsec: f64,
pub weighted_rms_ra_arcsec: f64,
pub weighted_rms_dec_arcsec: f64,
pub weighted_rms_combined_arcsec: f64,
pub mean_ra_arcsec: f64,
pub mean_dec_arcsec: f64,
pub std_ra_arcsec: f64,
pub std_dec_arcsec: f64,
pub rms_along_track_arcsec: f64,
pub rms_cross_track_arcsec: f64,
}
impl ResidualSummary {
pub(super) fn from_ffi(s: &empyrean_sys::EmpyreanResidualSummary) -> Self {
Self {
num_obs: s.num_obs,
num_selected: s.num_selected,
num_rejected: s.num_rejected,
chi2: s.chi2,
dof: s.dof,
reduced_chi2: s.reduced_chi2,
rms_ra_arcsec: s.rms_ra_arcsec,
rms_dec_arcsec: s.rms_dec_arcsec,
rms_combined_arcsec: s.rms_combined_arcsec,
weighted_rms_ra_arcsec: s.weighted_rms_ra_arcsec,
weighted_rms_dec_arcsec: s.weighted_rms_dec_arcsec,
weighted_rms_combined_arcsec: s.weighted_rms_combined_arcsec,
mean_ra_arcsec: s.mean_ra_arcsec,
mean_dec_arcsec: s.mean_dec_arcsec,
std_ra_arcsec: s.std_ra_arcsec,
std_dec_arcsec: s.std_dec_arcsec,
rms_along_track_arcsec: s.rms_along_track_arcsec,
rms_cross_track_arcsec: s.rms_cross_track_arcsec,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CovarianceRepresentation {
Cartesian,
Keplerian,
Cometary,
Spherical,
}
impl CovarianceRepresentation {
pub(super) fn from_int(v: i32) -> Self {
match v {
0 => Self::Cartesian,
1 => Self::Keplerian,
2 => Self::Cometary,
_ => Self::Spherical,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolveForParams {
StateOnly,
StateAndNonGrav,
Auto,
}
impl SolveForParams {
pub(super) fn from_int(v: i32) -> Self {
match v {
0 => Self::StateOnly,
1 => Self::StateAndNonGrav,
_ => Self::Auto,
}
}
pub(super) fn to_int(self) -> i32 {
match self {
Self::StateOnly => 0,
Self::StateAndNonGrav => 1,
Self::Auto => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum OutputEpoch {
#[default]
MidArc,
LastObservation,
IODEpoch,
Epoch(f64),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum OriginPolicy {
#[default]
Auto,
Explicit(crate::coordinate::Origin),
}
impl From<crate::coordinate::Origin> for OriginPolicy {
fn from(origin: crate::coordinate::Origin) -> Self {
Self::Explicit(origin)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AcceptabilityReport {
pub fit_acceptable: bool,
pub extrapolation_acceptable: bool,
pub converged_ok: bool,
pub reduced_chi2_ok: bool,
pub reduced_chi2_value: f64,
pub reduced_chi2_threshold: f64,
pub rms_ok: bool,
pub rms_value_arcsec: f64,
pub rms_threshold_arcsec: f64,
pub residual_isotropy_ok: bool,
pub at_ct_ratio_value: f64,
pub at_ct_ratio_threshold: f64,
pub covariance_ok: bool,
pub arc_coverage_ok: bool,
pub arc_days_value: f64,
pub arc_days_threshold: f64,
pub fractional_sigma_a_ok: bool,
pub fractional_sigma_a_value: f64,
pub fractional_sigma_a_threshold: f64,
}
impl AcceptabilityReport {
pub(super) fn from_ffi(r: &empyrean_sys::EmpyreanAcceptabilityReport) -> Self {
Self {
fit_acceptable: r.fit_acceptable != 0,
extrapolation_acceptable: r.extrapolation_acceptable != 0,
converged_ok: r.converged_ok != 0,
reduced_chi2_ok: r.reduced_chi2_ok != 0,
reduced_chi2_value: r.reduced_chi2_value,
reduced_chi2_threshold: r.reduced_chi2_threshold,
rms_ok: r.rms_ok != 0,
rms_value_arcsec: r.rms_value_arcsec,
rms_threshold_arcsec: r.rms_threshold_arcsec,
residual_isotropy_ok: r.residual_isotropy_ok != 0,
at_ct_ratio_value: r.at_ct_ratio_value,
at_ct_ratio_threshold: r.at_ct_ratio_threshold,
covariance_ok: r.covariance_ok != 0,
arc_coverage_ok: r.arc_coverage_ok != 0,
arc_days_value: r.arc_days_value,
arc_days_threshold: r.arc_days_threshold,
fractional_sigma_a_ok: r.fractional_sigma_a_ok != 0,
fractional_sigma_a_value: r.fractional_sigma_a_value,
fractional_sigma_a_threshold: r.fractional_sigma_a_threshold,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StationBias {
pub obs_code: String,
pub n_obs: usize,
pub bias_ra_arcsec: f64,
pub sigma_ra_arcsec: f64,
pub bias_dec_arcsec: f64,
pub sigma_dec_arcsec: f64,
pub bias_timing_sec: Option<f64>,
pub sigma_timing_sec: Option<f64>,
pub significance: f64,
}
impl StationBias {
pub(super) fn from_ffi(b: &empyrean_sys::EmpyreanStationBias) -> Self {
let obs_code = if b.obs_code.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(b.obs_code) }
.to_string_lossy()
.into_owned()
};
let (bias_t, sigma_t) = if b.has_timing != 0 {
(Some(b.bias_timing_sec), Some(b.sigma_timing_sec))
} else {
(None, None)
};
Self {
obs_code,
n_obs: b.n_obs,
bias_ra_arcsec: b.bias_ra_arcsec,
sigma_ra_arcsec: b.sigma_ra_arcsec,
bias_dec_arcsec: b.bias_dec_arcsec,
sigma_dec_arcsec: b.sigma_dec_arcsec,
bias_timing_sec: bias_t,
sigma_timing_sec: sigma_t,
significance: b.significance,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DetermineResult {
pub orbit: Orbit,
pub residuals: Vec<ObservationResidual>,
pub summary: ResidualSummary,
pub iterations: u32,
pub update_norm: f64,
pub converged: bool,
pub covariance: [[f64; 6]; 6],
pub covariance_representation: CovarianceRepresentation,
pub covariance_9x9: Option<[[f64; 9]; 9]>,
pub non_grav_delta: Option<[f64; 3]>,
pub rejection_passes: u32,
pub num_oppositions_fit: u32,
pub force_model_used: ForceModelTier,
pub solve_for_used: SolveForParams,
pub acceptability: AcceptabilityReport,
pub station_biases: Vec<StationBias>,
}
impl DetermineResult {
pub fn state(&self) -> PropagatedState {
let st = &self.orbit.state;
let e = st.elements;
PropagatedState {
epoch: st.epoch,
position: [e[0], e[1], e[2]],
velocity: [e[3], e[4], e[5]],
origin: st.origin,
frame: st.frame,
covariance: Some(self.covariance),
stm: None,
stt: None,
resolved_kind: CovarianceKind::Linear,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EvaluateResult {
pub residuals: Vec<ObservationResidual>,
pub summary: ResidualSummary,
}