mod config;
mod debiasing;
mod nuisance;
mod observation;
mod rejection;
mod result;
mod weighting;
pub use config::{
AcceptabilityThresholds, AutoEscalationPolicy, IODConfig, ODConfig, PhotometryConfig,
};
pub use debiasing::{DebiasingConfig, DebiasingResolution};
pub use nuisance::StationRaDecConfig;
pub use observation::{Observation, Observations, RadarMeasurement, RadarObservation};
pub use rejection::{RejectionConfig, RejectionKind};
pub use result::{
AcceptabilityReport, BandStat, CovarianceRepresentation, CovarianceTrust, DetermineResult,
EvaluateResult, GateRecord, ObservationResidual, OriginPolicy, OutputEpoch, PhotometryModel,
PhotometryResult, RadarResidual, RadarResidualKind, RejectionReason, ResidualSummary, SolveFor,
SolveForParams, SolvedCovariance, StationBias, TrustGateEvent,
};
pub use weighting::{SigmaPolicy, WeightingConfig, WeightingLayer, WeightingPreset};
use std::ffi::CString;
use crate::context::Context;
use crate::error::{Error, Result};
use crate::orbit::Orbit;
use crate::propagate::ForceModelTier;
impl Context {
pub fn read_ades(&self, path_or_content: &str) -> Result<Observations> {
let _ = self; let c_input = CString::new(path_or_content)
.map_err(|_| Error::invalid_input("input contains a NUL byte"))?;
let mut ptr: *mut empyrean_sys::EmpyreanObservation = std::ptr::null_mut();
let mut num: usize = 0;
let mut radar_ptr: *mut empyrean_sys::EmpyreanRadarObservation = std::ptr::null_mut();
let mut radar_num: usize = 0;
let code = unsafe {
empyrean_sys::empyrean_read_ades(
c_input.as_ptr(),
&mut ptr,
&mut num,
&mut radar_ptr,
&mut radar_num,
)
};
if code != 0 {
return Err(Error::capture(code));
}
Ok(Observations::from_raw_parts(ptr, num, radar_ptr, radar_num))
}
pub fn determine(
&self,
observations: &Observations,
initial_orbits: Option<&[Orbit]>,
config: &ODConfig,
) -> Result<DetermineResult> {
let mut _orbit_keep: Vec<crate::orbit::OrbitFfiKeep> = Vec::new();
let ffi_initial: Option<Vec<_>> = match initial_orbits {
Some(orbs) => {
_orbit_keep.reserve(orbs.len());
let v: Vec<_> = orbs
.iter()
.map(|o| {
let (ffi, keep) = o.to_ffi_with_keep()?;
_orbit_keep.push(keep);
Ok(ffi)
})
.collect::<Result<Vec<_>>>()?;
Some(v)
}
None => None,
};
let (init_ptr, init_len) = match &ffi_initial {
Some(v) => (v.as_ptr(), v.len()),
None => (std::ptr::null(), 0),
};
let (obs_ptr, obs_len) = observations.as_ffi_slice();
let (radar_ptr, radar_len) = observations.as_radar_ffi_slice();
let mut result = empyrean_sys::EmpyreanODResult::default();
let (ffi_config, _perturbers_keep) = config.to_ffi_with();
let code = unsafe {
empyrean_sys::empyrean_determine(
self.as_raw(),
obs_ptr,
obs_len,
radar_ptr,
radar_len,
init_ptr,
init_len,
&ffi_config,
&mut result,
)
};
if code != 0 {
return Err(Error::capture(code));
}
let det = ffi_od_result_to_rust(&result);
unsafe { empyrean_sys::empyrean_od_result_free(&mut result) };
det
}
pub fn evaluate(
&self,
orbit: &Orbit,
observations: &Observations,
config: &ODConfig,
) -> Result<EvaluateResult> {
let (ffi_orbit, _orbit_keep) = orbit.to_ffi_with_keep()?;
let (obs_ptr, obs_len) = observations.as_ffi_slice();
let mut result = empyrean_sys::EmpyreanEvaluateResult::default();
let (ffi_config, _perturbers_keep) = config.to_ffi_with();
let code = unsafe {
empyrean_sys::empyrean_evaluate(
self.as_raw(),
&ffi_orbit,
obs_ptr,
obs_len,
&ffi_config,
&mut result,
)
};
if code != 0 {
return Err(Error::capture(code));
}
let residuals = unsafe {
std::slice::from_raw_parts(result.observations, result.num_observations)
.iter()
.map(ObservationResidual::from_ffi)
.collect()
};
let summary = ResidualSummary::from_ffi(&result.summary);
unsafe { empyrean_sys::empyrean_evaluate_result_free(&mut result) };
Ok(EvaluateResult { residuals, summary })
}
pub fn refine(
&self,
orbit: &Orbit,
observations: &Observations,
config: &ODConfig,
) -> Result<DetermineResult> {
let (ffi_orbit, _orbit_keep) = orbit.to_ffi_with_keep()?;
let (obs_ptr, obs_len) = observations.as_ffi_slice();
let mut result = empyrean_sys::EmpyreanODResult::default();
let (ffi_config, _perturbers_keep) = config.to_ffi_with();
let code = unsafe {
empyrean_sys::empyrean_refine(
self.as_raw(),
&ffi_orbit,
obs_ptr,
obs_len,
&ffi_config,
&mut result,
)
};
if code != 0 {
return Err(Error::capture(code));
}
let det = ffi_od_result_to_rust(&result);
unsafe { empyrean_sys::empyrean_od_result_free(&mut result) };
det
}
}
pub(crate) fn ffi_od_result_to_rust_pub(
result: &empyrean_sys::EmpyreanODResult,
) -> crate::error::Result<DetermineResult> {
ffi_od_result_to_rust(result)
}
fn ffi_od_result_to_rust(
result: &empyrean_sys::EmpyreanODResult,
) -> crate::error::Result<DetermineResult> {
let orbit = ffi_od_result_orbit(result)?;
let residuals = unsafe {
std::slice::from_raw_parts(result.observations, result.num_observations)
.iter()
.map(ObservationResidual::from_ffi)
.collect()
};
let summary = ResidualSummary::from_ffi(&result.summary);
let acceptability = AcceptabilityReport::from_ffi(&result.acceptability);
let force_model_used = match result.force_model_used {
0 => ForceModelTier::Approximate,
1 => ForceModelTier::Basic,
_ => ForceModelTier::Standard,
};
let station_biases: Vec<StationBias> =
if result.station_biases.is_null() || result.num_station_biases == 0 {
Vec::new()
} else {
unsafe {
std::slice::from_raw_parts(result.station_biases, result.num_station_biases)
.iter()
.map(StationBias::from_ffi)
.collect()
}
};
let solved_covariance = (result.has_solved_covariance != 0)
.then(|| SolvedCovariance::from_ffi(&result.solved_covariance));
let thrust_delta_m_per_s: Vec<[f64; 3]> =
result.thrust_delta_m_per_s[..result.thrust_delta_count as usize].to_vec();
let dv_frame = (result.thrust_delta_count > 0)
.then(|| crate::coordinate::int_to_frame(result.dv_frame).ok())
.flatten();
let photometry =
(result.has_photometry != 0).then(|| PhotometryResult::from_ffi(&result.photometry));
let covariance_trust = CovarianceTrust::from_ffi(result);
Ok(DetermineResult {
orbit,
residuals,
summary,
iterations: result.iterations,
update_norm: result.update_norm,
converged: result.converged != 0,
covariance: result.covariance,
covariance_representation: CovarianceRepresentation::from_int(
result.covariance_representation,
),
covariance_9x9: (result.has_covariance_9x9 != 0).then_some(result.covariance_9x9),
non_grav_delta: (result.has_non_grav_delta != 0).then_some(result.non_grav_delta),
rejection_passes: result.rejection_passes,
num_oppositions_fit: result.num_oppositions_fit,
force_model_used,
solve_for_used: SolveForParams::from_result(
result.solve_for_used,
solved_covariance.as_ref(),
),
acceptability,
station_biases,
solved_covariance,
dt_delta: (result.has_dt_delta != 0).then_some(result.dt_delta),
amrat_delta: (result.has_amrat_delta != 0).then_some(result.amrat_delta),
thrust_delta_m_per_s,
dv_frame,
photometry,
covariance_trust,
})
}
fn ffi_od_result_orbit(result: &empyrean_sys::EmpyreanODResult) -> crate::error::Result<Orbit> {
use crate::coordinate::Origin;
let s = &result.orbit;
let origin = Origin::from_naif_id(s.origin).ok_or_else(|| {
Error::invalid_input(format!(
"C ABI returned unknown NAIF id for origin: {}",
s.origin
))
})?;
let frame = crate::coordinate::int_to_frame(s.frame)?;
let mut state = crate::CoordinateState::cartesian(
crate::Epoch::from_mjd_tdb(s.epoch_mjd_tdb),
[s.x, s.y, s.z, s.vx, s.vy, s.vz],
frame,
origin,
);
if s.has_covariance != 0 {
state = state.with_covariance(s.covariance);
}
let mut orbit = Orbit::new(state);
if result.has_non_grav != 0 {
let ng = &result.non_grav;
orbit = orbit.with_nongrav(ng.a1, ng.a2, ng.a3);
if ng.ng_alpha != 0.0
|| ng.ng_r0 != 0.0
|| ng.ng_m != 0.0
|| ng.ng_n != 0.0
|| ng.ng_k != 0.0
{
orbit = orbit.with_g_function(ng.ng_alpha, ng.ng_r0, ng.ng_m, ng.ng_n, ng.ng_k);
}
if ng.has_dt != 0 {
orbit = orbit.with_non_grav_dt(Some(ng.non_grav_dt));
}
if ng.has_covariance != 0 {
orbit = orbit.with_nongrav_covariance(Some(ng.covariance));
}
}
if result.has_srp != 0 {
let srp = &result.srp;
orbit = orbit.with_srp(srp.amrat, srp.cr);
if srp.has_amrat_variance != 0 {
orbit = orbit.with_srp_amrat_variance(Some(srp.amrat_variance));
}
}
Ok(orbit)
}