diff --git a/crates/lox-space/src/prelude.rs b/crates/lox-space/src/prelude.rs index 031eff47..b9f9c620 100644 --- a/crates/lox-space/src/prelude.rs +++ b/crates/lox-space/src/prelude.rs @@ -7,7 +7,8 @@ */ pub use lox_core::bodies::*; -pub use lox_core::coords::{Cartesian, DVec3, Keplerian}; +pub use lox_core::coords::two_body::{Cartesian, Keplerian}; +pub use lox_core::coords::DVec3; pub use lox_core::frames::*; pub use lox_core::time::dates::*; pub use lox_core::time::epochs::*; diff --git a/crates/lox_core/src/coords.rs b/crates/lox_core/src/coords.rs index 09c2c582..a59cce1b 100644 --- a/crates/lox_core/src/coords.rs +++ b/crates/lox_core/src/coords.rs @@ -8,15 +8,12 @@ pub use glam::DVec3; -use states::{CartesianState, KeplerianState}; - use crate::bodies::PointMass; -use crate::coords::states::TwoBodyState; -use crate::frames::{InertialFrame, ReferenceFrame}; -use crate::time::epochs::Epoch; +use crate::frames::ReferenceFrame; pub mod anomalies; pub mod states; +pub mod two_body; pub trait CoordinateSystem { type Origin: PointMass; @@ -25,322 +22,3 @@ pub trait CoordinateSystem { fn origin(&self) -> Self::Origin; fn reference_frame(&self) -> Self::Frame; } - -pub trait TwoBody -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - fn to_cartesian(&self) -> Cartesian; - - fn to_keplerian(&self) -> Keplerian; -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Cartesian -where - T: PointMass + Copy, - S: ReferenceFrame + Copy, -{ - state: CartesianState, - origin: T, - frame: S, -} - -impl Cartesian -where - T: PointMass + Copy, - S: ReferenceFrame + Copy, -{ - pub fn new(time: Epoch, origin: T, frame: S, position: DVec3, velocity: DVec3) -> Self { - let state = CartesianState::new(time, position, velocity); - Self { - state, - origin, - frame, - } - } - - pub fn time(&self) -> Epoch { - self.state.time() - } - - pub fn position(&self) -> DVec3 { - self.state.position() - } - - pub fn velocity(&self) -> DVec3 { - self.state.position() - } -} - -impl TwoBody for Cartesian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - fn to_cartesian(&self) -> Cartesian { - *self - } - - fn to_keplerian(&self) -> Keplerian { - Keplerian::from(*self) - } -} - -impl CoordinateSystem for Cartesian -where - T: PointMass + Copy, - S: ReferenceFrame + Copy, -{ - type Origin = T; - type Frame = S; - - fn origin(&self) -> Self::Origin { - self.origin - } - - fn reference_frame(&self) -> Self::Frame { - self.frame - } -} - -impl From> for Cartesian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - fn from(keplerian: Keplerian) -> Self { - let grav_param = keplerian.origin.gravitational_parameter(); - let state = keplerian.state.to_cartesian_state(grav_param); - Cartesian { - state, - origin: keplerian.origin, - frame: keplerian.frame, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Keplerian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - state: KeplerianState, - origin: T, - frame: S, -} - -impl Keplerian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - #[allow(clippy::too_many_arguments)] - pub fn new( - time: Epoch, - origin: T, - frame: S, - semi_major: f64, - eccentricity: f64, - inclination: f64, - ascending_node: f64, - periapsis_arg: f64, - true_anomaly: f64, - ) -> Self { - let state = KeplerianState::new( - time, - semi_major, - eccentricity, - inclination, - ascending_node, - periapsis_arg, - true_anomaly, - ); - Self { - state, - origin, - frame, - } - } - - pub fn time(&self) -> Epoch { - self.state.time() - } - - pub fn semi_major_axis(&self) -> f64 { - self.state.semi_major_axis() - } - - pub fn eccentricity(&self) -> f64 { - self.state.eccentricity() - } - - pub fn inclination(&self) -> f64 { - self.state.inclination() - } - - pub fn ascending_node(&self) -> f64 { - self.state.ascending_node() - } - - pub fn periapsis_argument(&self) -> f64 { - self.state.periapsis_argument() - } - - pub fn true_anomaly(&self) -> f64 { - self.state.true_anomaly() - } -} - -impl TwoBody for Keplerian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - fn to_cartesian(&self) -> Cartesian { - Cartesian::from(*self) - } - - fn to_keplerian(&self) -> Keplerian { - *self - } -} - -impl CoordinateSystem for Keplerian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - type Origin = T; - type Frame = S; - - fn origin(&self) -> Self::Origin { - self.origin - } - - fn reference_frame(&self) -> Self::Frame { - self.frame - } -} - -impl From> for Keplerian -where - T: PointMass + Copy, - S: InertialFrame + Copy, -{ - fn from(cartesian: Cartesian) -> Self { - let grav_param = cartesian.origin.gravitational_parameter(); - let state = cartesian.state.to_keplerian_state(grav_param); - Self { - state, - origin: cartesian.origin, - frame: cartesian.frame, - } - } -} - -#[cfg(test)] -mod tests { - use float_eq::assert_float_eq; - - use crate::bodies::Earth; - use crate::frames::Icrf; - use crate::time::dates::{Date, Time}; - use crate::time::epochs::TimeScale; - - use super::*; - - #[test] - fn test_cartesian() { - let date = Date::new(2023, 3, 25).expect("Date should be valid"); - let time = Time::new(21, 8, 0).expect("Time should be valid"); - let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time); - let pos = DVec3::new( - -0.107622532467967e7, - -0.676589636432773e7, - -0.332308783350379e6, - ) * 1e-3; - let vel = DVec3::new( - 0.935685775154103e4, - -0.331234775037644e4, - -0.118801577532701e4, - ) * 1e-3; - - let cartesian = Cartesian::new(epoch, Earth, Icrf, pos, vel); - let cartesian1 = cartesian.to_keplerian().to_cartesian(); - - assert_eq!(cartesian1.time(), epoch); - assert_eq!(cartesian1.origin(), Earth); - assert_eq!(cartesian1.reference_frame(), Icrf); - - assert_float_eq!(cartesian.position().x, cartesian1.position().x, rel <= 1e-8); - assert_float_eq!(cartesian.position().y, cartesian1.position().y, rel <= 1e-8); - assert_float_eq!(cartesian.position().z, cartesian1.position().z, rel <= 1e-8); - assert_float_eq!(cartesian.velocity().x, cartesian1.velocity().x, rel <= 1e-6); - assert_float_eq!(cartesian.velocity().y, cartesian1.velocity().y, rel <= 1e-6); - assert_float_eq!(cartesian.velocity().z, cartesian1.velocity().z, rel <= 1e-6); - } - - #[test] - fn test_keplerian() { - let date = Date::new(2023, 3, 25).expect("Date should be valid"); - let time = Time::new(21, 8, 0).expect("Time should be valid"); - let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time); - let semi_major = 24464560.0e-3; - let eccentricity = 0.7311; - let inclination = 0.122138; - let ascending_node = 1.00681; - let periapsis_arg = 3.10686; - let true_anomaly = 0.44369564302687126; - - let keplerian = Keplerian::new( - epoch, - Earth, - Icrf, - semi_major, - eccentricity, - inclination, - ascending_node, - periapsis_arg, - true_anomaly, - ); - let keplerian1 = keplerian.to_cartesian().to_keplerian(); - - assert_eq!(keplerian1.time(), epoch); - assert_eq!(keplerian1.origin(), Earth); - assert_eq!(keplerian1.reference_frame(), Icrf); - - assert_float_eq!( - keplerian.semi_major_axis(), - keplerian1.semi_major_axis(), - rel <= 1e-6 - ); - assert_float_eq!( - keplerian.eccentricity(), - keplerian1.eccentricity(), - abs <= 1e-6 - ); - assert_float_eq!( - keplerian.inclination(), - keplerian1.inclination(), - rel <= 1e-6 - ); - assert_float_eq!( - keplerian.ascending_node(), - keplerian1.ascending_node(), - rel <= 1e-6 - ); - assert_float_eq!( - keplerian.periapsis_argument(), - keplerian1.periapsis_argument(), - rel <= 1e-6 - ); - assert_float_eq!( - keplerian.true_anomaly(), - keplerian1.true_anomaly(), - rel <= 1e-6 - ); - } -} diff --git a/crates/lox_core/src/coords/states.rs b/crates/lox_core/src/coords/states.rs index 1adaf47f..275f3c8f 100644 --- a/crates/lox_core/src/coords/states.rs +++ b/crates/lox_core/src/coords/states.rs @@ -261,7 +261,10 @@ mod tests { -0.331234775037644e4, -0.118801577532701e4, ); + let cartesian = CartesianState::new(time, pos, vel); + assert_eq!(cartesian.to_cartesian_state(grav_param), cartesian); + let keplerian = KeplerianState::new( time, semi_major, @@ -271,10 +274,14 @@ mod tests { periapsis_arg, true_anomaly, ); + assert_eq!(keplerian.to_keplerian_state(grav_param), keplerian); let cartesian1 = keplerian.to_cartesian_state(grav_param); let keplerian1 = cartesian.to_keplerian_state(grav_param); + assert_eq!(cartesian1.time(), time); + assert_eq!(keplerian1.time(), time); + assert_float_eq!(pos.x, cartesian1.position.x, rel <= 1e-8); assert_float_eq!(pos.y, cartesian1.position.y, rel <= 1e-8); assert_float_eq!(pos.z, cartesian1.position.z, rel <= 1e-8); diff --git a/crates/lox_core/src/coords/two_body.rs b/crates/lox_core/src/coords/two_body.rs new file mode 100644 index 00000000..1e000c66 --- /dev/null +++ b/crates/lox_core/src/coords/two_body.rs @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2024. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use glam::DVec3; + +use crate::bodies::PointMass; +use crate::coords::states::{CartesianState, KeplerianState, TwoBodyState}; +use crate::coords::CoordinateSystem; +use crate::frames::{InertialFrame, ReferenceFrame}; +use crate::time::epochs::Epoch; + +pub trait TwoBody +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + fn to_cartesian(&self) -> Cartesian; + + fn to_keplerian(&self) -> Keplerian; +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Cartesian +where + T: PointMass + Copy, + S: ReferenceFrame + Copy, +{ + state: CartesianState, + origin: T, + frame: S, +} + +impl Cartesian +where + T: PointMass + Copy, + S: ReferenceFrame + Copy, +{ + pub fn new(time: Epoch, origin: T, frame: S, position: DVec3, velocity: DVec3) -> Self { + let state = CartesianState::new(time, position, velocity); + Self { + state, + origin, + frame, + } + } + + pub fn time(&self) -> Epoch { + self.state.time() + } + + pub fn position(&self) -> DVec3 { + self.state.position() + } + + pub fn velocity(&self) -> DVec3 { + self.state.position() + } +} + +impl TwoBody for Cartesian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + fn to_cartesian(&self) -> Cartesian { + *self + } + + fn to_keplerian(&self) -> Keplerian { + Keplerian::from(*self) + } +} + +impl CoordinateSystem for Cartesian +where + T: PointMass + Copy, + S: ReferenceFrame + Copy, +{ + type Origin = T; + type Frame = S; + + fn origin(&self) -> Self::Origin { + self.origin + } + + fn reference_frame(&self) -> Self::Frame { + self.frame + } +} + +impl From> for Cartesian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + fn from(keplerian: Keplerian) -> Self { + let grav_param = keplerian.origin.gravitational_parameter(); + let state = keplerian.state.to_cartesian_state(grav_param); + Cartesian { + state, + origin: keplerian.origin, + frame: keplerian.frame, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Keplerian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + state: KeplerianState, + origin: T, + frame: S, +} + +impl Keplerian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + time: Epoch, + origin: T, + frame: S, + semi_major: f64, + eccentricity: f64, + inclination: f64, + ascending_node: f64, + periapsis_arg: f64, + true_anomaly: f64, + ) -> Self { + let state = KeplerianState::new( + time, + semi_major, + eccentricity, + inclination, + ascending_node, + periapsis_arg, + true_anomaly, + ); + Self { + state, + origin, + frame, + } + } + + pub fn time(&self) -> Epoch { + self.state.time() + } + + pub fn semi_major_axis(&self) -> f64 { + self.state.semi_major_axis() + } + + pub fn eccentricity(&self) -> f64 { + self.state.eccentricity() + } + + pub fn inclination(&self) -> f64 { + self.state.inclination() + } + + pub fn ascending_node(&self) -> f64 { + self.state.ascending_node() + } + + pub fn periapsis_argument(&self) -> f64 { + self.state.periapsis_argument() + } + + pub fn true_anomaly(&self) -> f64 { + self.state.true_anomaly() + } +} + +impl TwoBody for Keplerian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + fn to_cartesian(&self) -> Cartesian { + Cartesian::from(*self) + } + + fn to_keplerian(&self) -> Keplerian { + *self + } +} + +impl CoordinateSystem for Keplerian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + type Origin = T; + type Frame = S; + + fn origin(&self) -> Self::Origin { + self.origin + } + + fn reference_frame(&self) -> Self::Frame { + self.frame + } +} + +impl From> for Keplerian +where + T: PointMass + Copy, + S: InertialFrame + Copy, +{ + fn from(cartesian: Cartesian) -> Self { + let grav_param = cartesian.origin.gravitational_parameter(); + let state = cartesian.state.to_keplerian_state(grav_param); + Self { + state, + origin: cartesian.origin, + frame: cartesian.frame, + } + } +} + +#[cfg(test)] +mod tests { + use float_eq::assert_float_eq; + + use crate::bodies::Earth; + use crate::frames::Icrf; + use crate::time::dates::{Date, Time}; + use crate::time::epochs::TimeScale; + + use super::*; + + #[test] + fn test_cartesian() { + let date = Date::new(2023, 3, 25).expect("Date should be valid"); + let time = Time::new(21, 8, 0).expect("Time should be valid"); + let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time); + let pos = DVec3::new( + -0.107622532467967e7, + -0.676589636432773e7, + -0.332308783350379e6, + ) * 1e-3; + let vel = DVec3::new( + 0.935685775154103e4, + -0.331234775037644e4, + -0.118801577532701e4, + ) * 1e-3; + + let cartesian = Cartesian::new(epoch, Earth, Icrf, pos, vel); + let cartesian1 = cartesian.to_keplerian().to_cartesian(); + + assert_eq!(cartesian1.time(), epoch); + assert_eq!(cartesian1.origin(), Earth); + assert_eq!(cartesian1.reference_frame(), Icrf); + + assert_float_eq!(cartesian.position().x, cartesian1.position().x, rel <= 1e-8); + assert_float_eq!(cartesian.position().y, cartesian1.position().y, rel <= 1e-8); + assert_float_eq!(cartesian.position().z, cartesian1.position().z, rel <= 1e-8); + assert_float_eq!(cartesian.velocity().x, cartesian1.velocity().x, rel <= 1e-6); + assert_float_eq!(cartesian.velocity().y, cartesian1.velocity().y, rel <= 1e-6); + assert_float_eq!(cartesian.velocity().z, cartesian1.velocity().z, rel <= 1e-6); + } + + #[test] + fn test_keplerian() { + let date = Date::new(2023, 3, 25).expect("Date should be valid"); + let time = Time::new(21, 8, 0).expect("Time should be valid"); + let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time); + let semi_major = 24464560.0e-3; + let eccentricity = 0.7311; + let inclination = 0.122138; + let ascending_node = 1.00681; + let periapsis_arg = 3.10686; + let true_anomaly = 0.44369564302687126; + + let keplerian = Keplerian::new( + epoch, + Earth, + Icrf, + semi_major, + eccentricity, + inclination, + ascending_node, + periapsis_arg, + true_anomaly, + ); + let keplerian1 = keplerian.to_cartesian().to_keplerian(); + + assert_eq!(keplerian1.time(), epoch); + assert_eq!(keplerian1.origin(), Earth); + assert_eq!(keplerian1.reference_frame(), Icrf); + + assert_float_eq!( + keplerian.semi_major_axis(), + keplerian1.semi_major_axis(), + rel <= 1e-6 + ); + assert_float_eq!( + keplerian.eccentricity(), + keplerian1.eccentricity(), + abs <= 1e-6 + ); + assert_float_eq!( + keplerian.inclination(), + keplerian1.inclination(), + rel <= 1e-6 + ); + assert_float_eq!( + keplerian.ascending_node(), + keplerian1.ascending_node(), + rel <= 1e-6 + ); + assert_float_eq!( + keplerian.periapsis_argument(), + keplerian1.periapsis_argument(), + rel <= 1e-6 + ); + assert_float_eq!( + keplerian.true_anomaly(), + keplerian1.true_anomaly(), + rel <= 1e-6 + ); + } +} diff --git a/crates/lox_py/src/bodies.rs b/crates/lox_py/src/bodies.rs index e6f8f3b9..3ae8cee1 100644 --- a/crates/lox_py/src/bodies.rs +++ b/crates/lox_py/src/bodies.rs @@ -675,5 +675,9 @@ mod tests { .try_into() .expect("minor_body is valid"); assert_eq!(minor_body1.id(), minor_body.id()); + + let obj = Python::with_gil(|py| 1.into_py(py)); + let body = PyBody::try_from(obj); + assert!(body.is_err()); } }