From da8ef2dd7b3ecac49e1d6362e4985b3246893038 Mon Sep 17 00:00:00 2001 From: Dar Dahlen Date: Tue, 22 Oct 2024 10:40:56 -0700 Subject: [PATCH] Add more thorough FOV tests (#142) * Add more tests for FOV checks * changelog * More documentation for simultaneous states --- CHANGELOG.md | 4 ++ src/kete/rust/simult_states.rs | 4 ++ src/kete_core/src/flux/reflected.rs | 8 ++-- src/kete_core/src/fov/fov_like.rs | 6 +-- src/kete_core/src/fov/generic.rs | 68 ++++++++++++++++++++++++++++- 5 files changed, 81 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ef9924..b13998e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for loading orbit information from JPL Horizons +- Added more complete testing for light delay computations in the various FOV checks. ### Changed @@ -23,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 assuming that the epoch time of the covariance fits is in UTC. This assumption is now included in the covariance matrix sampling. This is different than their other data products. +- Field of View checks for SPK checks was not returning the correct light delayed time, + it was returning the position/velocity at the observed position, but the time was + the instantaneous time. ## [v1.0.2] diff --git a/src/kete/rust/simult_states.rs b/src/kete/rust/simult_states.rs index ccdee5f..5d30238 100644 --- a/src/kete/rust/simult_states.rs +++ b/src/kete/rust/simult_states.rs @@ -14,6 +14,10 @@ use crate::{fovs::AllowedFOV, frame::PyFrames, state::PyState}; /// The main value in this is that also includes an optional Field of View. /// If the FOV is provided, it is implied that the states which are present /// in this file were objects seen by the FOV. +/// +/// In the case where the FOV is provided, it is expected that the states +/// positions will include light delay, so an object which is ~1au away from +/// the FOV observer will have a JD which is offset by about 8 minutes. /// #[pyclass(module = "kete", frozen, sequence, name = "SimultaneousStates")] #[derive(Debug)] diff --git a/src/kete_core/src/flux/reflected.rs b/src/kete_core/src/flux/reflected.rs index 4de1fd5..9fe5119 100644 --- a/src/kete_core/src/flux/reflected.rs +++ b/src/kete_core/src/flux/reflected.rs @@ -8,9 +8,9 @@ use nalgebra::Vector3; use serde::{Deserialize, Serialize}; /// This computes the phase curve correction using the IAU standard for the HG model. -/// +/// /// Specifically page Page 550 - Equation (A4): -/// +/// /// Asteroids II. University of Arizona Press, Tucson, pp. 524–556. /// Bowell, E., Hapke, B., Domingue, D., Lumme, K., Peltoniemi, J., Harris, /// A.W., 1989. Application of photometric models to asteroids, in: Binzel, @@ -37,9 +37,9 @@ pub fn hg_phase_curve_correction(g_param: f64, phase: f64) -> f64 { /// /// H, Albedo, and Diameter are all related by the relation: /// diameter = c_hg / albedo.sqrt() * (10f64).powf(-h_mag / 5.0); -/// +/// /// Specifically page Page 549 - Equation (A1) of: -/// +/// /// Asteroids II. University of Arizona Press, Tucson, pp. 524–556. /// Bowell, E., Hapke, B., Domingue, D., Lumme, K., Peltoniemi, J., Harris, /// A.W., 1989. Application of photometric models to asteroids, in: Binzel, diff --git a/src/kete_core/src/fov/fov_like.rs b/src/kete_core/src/fov/fov_like.rs index 46dc61f..bd8f4be 100644 --- a/src/kete_core/src/fov/fov_like.rs +++ b/src/kete_core/src/fov/fov_like.rs @@ -56,7 +56,7 @@ pub trait FovLike: Sync + Sized { let (idx, contains) = self.contains(&new_rel_pos); let new_state = State::new( state.desig.clone(), - obs.jd, + obs.jd + dt, new_pos, vel, obs.frame, @@ -203,8 +203,8 @@ pub trait FovLike: Sync + Sized { .into_par_iter() .filter_map(|&obj_id| { match spk.try_get_state(obj_id, obs.jd, obs.center_id, obs.frame) { - Ok(state) => match self.check_linear(&state) { - (idx, Contains::Inside, state) => Some((idx, state)), + Ok(state) => match self.check_two_body(&state) { + Ok((idx, Contains::Inside, state)) => Some((idx, state)), _ => None, }, _ => None, diff --git a/src/kete_core/src/fov/generic.rs b/src/kete_core/src/fov/generic.rs index 96d1fcb..cee6ed0 100644 --- a/src/kete_core/src/fov/generic.rs +++ b/src/kete_core/src/fov/generic.rs @@ -192,12 +192,12 @@ impl FovLike for GenericCone { #[cfg(test)] mod tests { use super::*; - use crate::constants::GMS_SQRT; + use crate::constants::{self, GMS_SQRT}; use crate::prelude::*; use crate::state::Desig; #[test] - fn test_check_visible() { + fn test_check_rectangle_visible() { let circular = State::new( Desig::Empty, 2451545.0, @@ -237,4 +237,68 @@ mod tests { .is_some()); } } + + /// Test the light delay computations for the different checks + #[test] + fn test_check_omni_visible() { + // Build an observer, and check the observability of ceres with different offsets from the observer time. + // this will exercise the position, velocity, and time offsets due to light delay. + let spk = get_spk_singleton().read().unwrap(); + let observer = State::new( + Desig::Empty, + 2451545.0, + [0.0, 1., 0.0].into(), + [-GMS_SQRT, 0.0, 0.0].into(), + Frame::Ecliptic, + 10, + ); + + for offset in [-10.0, -5.0, 0.0, 5.0, 10.0] { + let ceres = spk + .try_get_state(20000001, observer.jd + offset, 10, Frame::Ecliptic) + .unwrap(); + + let fov = OmniDirectional::new(observer.clone()); + + // Check two body approximation calculation + let two_body = fov.check_two_body(&ceres); + assert!(two_body.is_ok()); + let (_, _, two_body) = two_body.unwrap(); + let dist = (Vector3::from(two_body.pos) - Vector3::from(observer.pos)).norm(); + assert!((observer.jd - two_body.jd - dist * constants::C_AU_PER_DAY_INV).abs() < 1e-6); + let ceres_exact = spk + .try_get_state(20000001, two_body.jd, 10, Frame::Ecliptic) + .unwrap(); + // check that we are within about 150km - not bad for 2 body + assert!((Vector3::from(two_body.pos) - Vector3::from(ceres_exact.pos)).norm() < 1e-6); + + // Check n body approximation calculation + let n_body = fov.check_n_body(&ceres, false); + assert!(n_body.is_ok()); + let (_, _, n_body) = n_body.unwrap(); + assert!((observer.jd - n_body.jd - dist * constants::C_AU_PER_DAY_INV).abs() < 1e-6); + let ceres_exact = spk + .try_get_state(20000001, n_body.jd, 10, Frame::Ecliptic) + .unwrap(); + // check that we are within about 150m + assert!((Vector3::from(n_body.pos) - Vector3::from(ceres_exact.pos)).norm() < 1e-9); + + // Check spk queries + let spk_check = &fov.check_spks(&[20000001])[0]; + assert!(spk_check.is_some()); + let spk_check = &spk_check.as_ref().unwrap().states[0]; + assert!((observer.jd - spk_check.jd - dist * constants::C_AU_PER_DAY_INV).abs() < 1e-6); + let ceres_exact = spk + .try_get_state(20000001, spk_check.jd, 10, Frame::Ecliptic) + .unwrap(); + // check that we are within about 150 micron + assert!((Vector3::from(spk_check.pos) - Vector3::from(ceres_exact.pos)).norm() < 1e-12); + + assert!(fov + .check_visible(&[ceres], 6.0, false) + .first() + .unwrap() + .is_some()); + } + } }