use crate::coordinate::CoordinateState;
use crate::thrust::{ThrustArc, ThrustParams};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum PhaseFunction {
HG = 0,
HG1G2 = 1,
HG12 = 2,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SrpParams {
pub amrat: f64,
pub cr: f64,
pub amrat_variance: Option<f64>,
}
impl SrpParams {
pub(crate) fn from_ffi(amrat: f64, cr: f64, has_srp: u8, amrat_variance: f64) -> Option<Self> {
if has_srp == 0 {
return None;
}
Some(Self {
amrat,
cr,
amrat_variance: if amrat_variance.is_finite() && amrat_variance > 0.0 {
Some(amrat_variance)
} else {
None
},
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Orbit {
pub orbit_id: Option<String>,
pub object_id: Option<String>,
pub state: CoordinateState,
pub a1: f64,
pub a2: f64,
pub a3: f64,
pub ng_alpha: f64,
pub ng_r0: f64,
pub ng_m: f64,
pub ng_n: f64,
pub ng_k: f64,
pub non_grav_dt: Option<f64>,
pub non_grav_dt_variance: Option<f64>,
pub ng_covariance: Option<[[f64; 3]; 3]>,
pub phot_system: Option<PhaseFunction>,
pub h_mag: f64,
pub slope1: f64,
pub slope2: f64,
pub thrust: Option<ThrustParams>,
pub srp: Option<SrpParams>,
}
impl Orbit {
pub fn new(state: CoordinateState) -> Self {
Self {
orbit_id: None,
object_id: None,
state,
a1: 0.0,
a2: 0.0,
a3: 0.0,
ng_alpha: 0.0,
ng_r0: 0.0,
ng_m: 0.0,
ng_n: 0.0,
ng_k: 0.0,
non_grav_dt: None,
non_grav_dt_variance: None,
ng_covariance: None,
phot_system: None,
h_mag: f64::NAN,
slope1: 0.0,
slope2: 0.0,
thrust: None,
srp: None,
}
}
pub fn with_orbit_id(mut self, id: impl Into<String>) -> Self {
self.orbit_id = Some(id.into());
self
}
pub fn with_object_id(mut self, id: impl Into<String>) -> Self {
self.object_id = Some(id.into());
self
}
pub fn with_nongrav(mut self, a1: f64, a2: f64, a3: f64) -> Self {
self.a1 = a1;
self.a2 = a2;
self.a3 = a3;
self
}
pub fn with_g_function(mut self, alpha: f64, r0: f64, m: f64, n: f64, k: f64) -> Self {
self.ng_alpha = alpha;
self.ng_r0 = r0;
self.ng_m = m;
self.ng_n = n;
self.ng_k = k;
self
}
pub fn with_non_grav_dt(mut self, dt: Option<f64>) -> Self {
self.non_grav_dt = dt;
self
}
pub fn with_non_grav_dt_variance(mut self, v: Option<f64>) -> Self {
self.non_grav_dt_variance = v;
self
}
pub fn with_nongrav_covariance(mut self, covariance: Option<[[f64; 3]; 3]>) -> Self {
self.ng_covariance = covariance;
self
}
pub fn with_photometry(
mut self,
phot_system: PhaseFunction,
h: f64,
slope1: f64,
slope2: f64,
) -> Self {
self.phot_system = Some(phot_system);
self.h_mag = h;
self.slope1 = slope1;
self.slope2 = slope2;
self
}
pub fn with_hg(self, h: f64, g: f64) -> Self {
self.with_photometry(PhaseFunction::HG, h, g, 0.0)
}
pub fn with_thrust(mut self, thrust: Option<ThrustParams>) -> Self {
self.thrust = thrust;
self
}
pub fn with_srp(mut self, amrat: f64, cr: f64) -> Self {
let amrat_variance = self.srp.and_then(|s| s.amrat_variance);
self.srp = Some(SrpParams {
amrat,
cr,
amrat_variance,
});
self
}
pub fn with_srp_amrat_variance(mut self, v: Option<f64>) -> Self {
if let Some(srp) = self.srp.as_mut() {
srp.amrat_variance = v;
}
self
}
pub(crate) fn to_ffi_with_keep(
&self,
) -> crate::error::Result<(empyrean_sys::EmpyreanOrbit, OrbitFfiKeep)> {
use std::ffi::CString;
let (phase_int, h, s1, s2) = match self.phot_system {
Some(pf) => (pf as i32, self.h_mag, self.slope1, self.slope2),
None => (-1, f64::NAN, 0.0, 0.0),
};
let orbit_id_cstr =
CString::new(self.orbit_id.as_deref().unwrap_or("")).unwrap_or_default();
let object_id_cstr =
CString::new(self.object_id.as_deref().unwrap_or("")).unwrap_or_default();
let orbit_id_ptr = orbit_id_cstr.as_ptr();
let object_id_ptr = object_id_cstr.as_ptr();
let thrust_arcs: Vec<empyrean_sys::EmpyreanThrustArc> = self
.thrust
.as_ref()
.map(|tp| tp.arcs.iter().map(ThrustArc::to_ffi).collect())
.unwrap_or_default();
let dv_corrections: Vec<[f64; 3]> = self
.thrust
.as_ref()
.map(|tp| tp.dv_corrections.clone())
.unwrap_or_default();
let correction_covariances: Vec<[[f64; 3]; 3]> = self
.thrust
.as_ref()
.map(|tp| tp.correction_covariances.clone())
.unwrap_or_default();
let thrust_arcs_ptr = if thrust_arcs.is_empty() {
std::ptr::null()
} else {
thrust_arcs.as_ptr()
};
let dv_corrections_ptr = if dv_corrections.is_empty() {
std::ptr::null()
} else {
dv_corrections.as_ptr()
};
let correction_covariances_ptr = if correction_covariances.is_empty() {
std::ptr::null()
} else {
correction_covariances.as_ptr()
};
let ffi = empyrean_sys::EmpyreanOrbit {
state: self.state.to_ffi()?,
orbit_id: orbit_id_ptr,
object_id: object_id_ptr,
a1: self.a1,
a2: self.a2,
a3: self.a3,
ng_alpha: self.ng_alpha,
ng_r0: self.ng_r0,
ng_m: self.ng_m,
ng_n: self.ng_n,
ng_k: self.ng_k,
non_grav_dt: self.non_grav_dt.unwrap_or(f64::NAN),
non_grav_dt_variance: self.non_grav_dt_variance.unwrap_or(f64::NAN),
has_non_grav_covariance: u8::from(self.ng_covariance.is_some()),
non_grav_covariance: self.ng_covariance.unwrap_or([[0.0; 3]; 3]),
phot_system: phase_int,
h_mag: h,
slope1: s1,
slope2: s2,
thrust_arcs: thrust_arcs_ptr,
n_thrust_arcs: thrust_arcs.len(),
dv_corrections: dv_corrections_ptr,
n_dv_corrections: dv_corrections.len(),
correction_covariances: correction_covariances_ptr,
n_correction_covariances: correction_covariances.len(),
has_srp: u8::from(self.srp.is_some()),
srp_amrat: self.srp.map(|s| s.amrat).unwrap_or(0.0),
srp_cr: self.srp.map(|s| s.cr).unwrap_or(0.0),
srp_amrat_variance: self.srp.and_then(|s| s.amrat_variance).unwrap_or(f64::NAN),
};
let keep = OrbitFfiKeep {
_orbit_id: orbit_id_cstr,
_object_id: object_id_cstr,
_thrust_arcs: thrust_arcs,
_dv_corrections: dv_corrections,
_correction_covariances: correction_covariances,
};
Ok((ffi, keep))
}
}
pub(crate) struct OrbitFfiKeep {
_orbit_id: std::ffi::CString,
_object_id: std::ffi::CString,
_thrust_arcs: Vec<empyrean_sys::EmpyreanThrustArc>,
_dv_corrections: Vec<[f64; 3]>,
_correction_covariances: Vec<[[f64; 3]; 3]>,
}
pub(crate) fn orbits_to_ffi(
orbits: &[Orbit],
) -> crate::error::Result<(Vec<empyrean_sys::EmpyreanOrbit>, Vec<OrbitFfiKeep>)> {
let mut keep: Vec<OrbitFfiKeep> = Vec::with_capacity(orbits.len());
let ffi = orbits
.iter()
.map(|o| {
let (f, k) = o.to_ffi_with_keep()?;
keep.push(k);
Ok(f)
})
.collect::<crate::error::Result<Vec<_>>>()?;
Ok((ffi, keep))
}
#[cfg(test)]
mod srp_tests {
use super::*;
use crate::Epoch;
use crate::coordinate::{CoordinateState, Frame, Origin};
fn cartesian_orbit() -> Orbit {
Orbit::new(CoordinateState::cartesian(
Epoch::from_mjd_tdb(59000.0),
[1.0, 0.1, 0.05, -0.005, 0.015, 0.001],
Frame::EclipticJ2000,
Origin::Sun,
))
}
#[test]
fn no_srp_marshals_absent() {
let (ffi, _keep) = cartesian_orbit().to_ffi_with_keep().unwrap();
assert_eq!(ffi.has_srp, 0);
assert!(
SrpParams::from_ffi(
ffi.srp_amrat,
ffi.srp_cr,
ffi.has_srp,
ffi.srp_amrat_variance
)
.is_none()
);
}
#[test]
fn fixed_force_srp_round_trips() {
let o = cartesian_orbit().with_srp(3.0e-3, 1.2);
let (ffi, _keep) = o.to_ffi_with_keep().unwrap();
assert_eq!(ffi.has_srp, 1);
assert_eq!(ffi.srp_amrat, 3.0e-3);
assert_eq!(ffi.srp_cr, 1.2);
assert!(ffi.srp_amrat_variance.is_nan()); let back = SrpParams::from_ffi(
ffi.srp_amrat,
ffi.srp_cr,
ffi.has_srp,
ffi.srp_amrat_variance,
)
.unwrap();
assert_eq!(
back,
SrpParams {
amrat: 3.0e-3,
cr: 1.2,
amrat_variance: None
}
);
}
#[test]
fn fittable_srp_round_trips() {
let o = cartesian_orbit()
.with_srp(3.0e-3, 1.0)
.with_srp_amrat_variance(Some(1.0e-8));
let (ffi, _keep) = o.to_ffi_with_keep().unwrap();
assert_eq!(ffi.srp_amrat_variance, 1.0e-8);
let back = SrpParams::from_ffi(
ffi.srp_amrat,
ffi.srp_cr,
ffi.has_srp,
ffi.srp_amrat_variance,
)
.unwrap();
assert_eq!(back.amrat_variance, Some(1.0e-8));
}
#[test]
fn amrat_variance_without_srp_is_noop() {
let o = cartesian_orbit().with_srp_amrat_variance(Some(1.0e-8));
assert!(o.srp.is_none());
}
}