use astrodyn_quantities::aliases::{Position, Velocity};
use astrodyn_quantities::frame::{Frame, IntegrationFrame, RootInertial};
use astrodyn_quantities::integ_origin::IntegOrigin;
use glam::DVec3;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TranslationalState {
pub position: DVec3,
pub velocity: DVec3,
}
impl TranslationalState {
pub fn is_likely_uninitialized(&self) -> bool {
self.position == DVec3::ZERO && self.velocity == DVec3::ZERO
}
}
pub struct TranslationalStateTyped<F: Frame = RootInertial> {
pub position: Position<F>,
pub velocity: Velocity<F>,
}
impl<F: Frame> core::fmt::Debug for TranslationalStateTyped<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TranslationalStateTyped")
.field("position", &self.position)
.field("velocity", &self.velocity)
.finish()
}
}
impl<F: Frame> Clone for TranslationalStateTyped<F> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<F: Frame> Copy for TranslationalStateTyped<F> {}
impl<F: Frame> PartialEq for TranslationalStateTyped<F> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.position == other.position && self.velocity == other.velocity
}
}
impl<F: Frame> Default for TranslationalStateTyped<F> {
#[inline]
fn default() -> Self {
Self {
position: Position::<F>::zero(),
velocity: Velocity::<F>::zero(),
}
}
}
impl<F: Frame> TranslationalStateTyped<F> {
#[inline]
pub fn is_likely_uninitialized(&self) -> bool {
self.position.raw_si() == DVec3::ZERO && self.velocity.raw_si() == DVec3::ZERO
}
#[inline]
pub fn relabel_to<F2: Frame>(self) -> TranslationalStateTyped<F2> {
TranslationalStateTyped {
position: self.position.relabel_to::<F2>(),
velocity: self.velocity.relabel_to::<F2>(),
}
}
#[inline]
pub fn to_untyped(&self) -> TranslationalState {
TranslationalState {
position: self.position.raw_si(),
velocity: self.velocity.raw_si(),
}
}
#[inline]
pub fn from_untyped_unchecked(s: &TranslationalState) -> Self {
Self {
position: Position::<F>::from_raw_si(s.position),
velocity: Velocity::<F>::from_raw_si(s.velocity),
}
}
}
impl TranslationalStateTyped<IntegrationFrame> {
#[inline]
pub fn to_inertial(&self, o: &IntegOrigin) -> TranslationalStateTyped<RootInertial> {
TranslationalStateTyped {
position: o.shift_position(self.position),
velocity: o.shift_velocity(self.velocity),
}
}
#[inline]
pub fn from_inertial(s: TranslationalStateTyped<RootInertial>, o: &IntegOrigin) -> Self {
Self {
position: Position::<IntegrationFrame>::from_raw_si(
s.position.raw_si() - o.position.raw_si(),
),
velocity: Velocity::<IntegrationFrame>::from_raw_si(
s.velocity.raw_si() - o.velocity.raw_si(),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_quantities::frame::Ecef;
#[test]
fn default_is_likely_uninitialized() {
assert!(TranslationalState::default().is_likely_uninitialized());
}
#[test]
fn nonzero_position_is_initialized() {
let s = TranslationalState {
position: DVec3::new(6.7e6, 0.0, 0.0),
velocity: DVec3::ZERO,
};
assert!(!s.is_likely_uninitialized());
}
#[test]
fn nonzero_velocity_is_initialized() {
let s = TranslationalState {
position: DVec3::ZERO,
velocity: DVec3::new(0.0, 7.5e3, 0.0),
};
assert!(!s.is_likely_uninitialized());
}
#[test]
fn typed_round_trips_through_untyped() {
let pos = DVec3::new(7e6, 0.0, 0.0);
let vel = DVec3::new(0.0, 7500.0, 0.0);
let typed = TranslationalStateTyped::<RootInertial> {
position: Position::<RootInertial>::from_raw_si(pos),
velocity: Velocity::<RootInertial>::from_raw_si(vel),
};
assert_eq!(typed.position.raw_si(), pos);
assert_eq!(typed.velocity.raw_si(), vel);
}
#[test]
fn typed_default_is_likely_uninitialized() {
let s = TranslationalStateTyped::<RootInertial>::default();
assert!(s.is_likely_uninitialized());
}
#[test]
fn typed_inertial_and_ecef_are_distinct_types() {
let s_inertial = TranslationalStateTyped::<RootInertial>::default();
let s_ecef = TranslationalStateTyped::<Ecef>::default();
assert!(s_inertial.is_likely_uninitialized());
assert!(s_ecef.is_likely_uninitialized());
}
#[test]
fn to_inertial_with_zero_origin_is_bit_identical() {
let s_integ = TranslationalStateTyped::<IntegrationFrame> {
position: Position::<IntegrationFrame>::from_raw_si(DVec3::new(7e6, 0.0, 0.0)),
velocity: Velocity::<IntegrationFrame>::from_raw_si(DVec3::new(0.0, 7500.0, 0.0)),
};
let o = IntegOrigin::zero();
let s_inertial = s_integ.to_inertial(&o);
assert_eq!(s_inertial.position.raw_si(), DVec3::new(7e6, 0.0, 0.0));
assert_eq!(s_inertial.velocity.raw_si(), DVec3::new(0.0, 7500.0, 0.0));
}
#[test]
fn from_inertial_round_trips_with_to_inertial() {
let s_integ = TranslationalStateTyped::<IntegrationFrame> {
position: Position::<IntegrationFrame>::from_raw_si(DVec3::new(7e6, 0.0, 0.0)),
velocity: Velocity::<IntegrationFrame>::from_raw_si(DVec3::new(0.0, 7500.0, 0.0)),
};
let o = IntegOrigin {
position: Position::<RootInertial>::from_raw_si(DVec3::new(1.5e11, 0.0, 0.0)),
velocity: Velocity::<RootInertial>::from_raw_si(DVec3::new(0.0, 30_000.0, 0.0)),
};
let s_inertial = s_integ.to_inertial(&o);
let s_back = TranslationalStateTyped::<IntegrationFrame>::from_inertial(s_inertial, &o);
assert_eq!(s_back.position.raw_si(), s_integ.position.raw_si());
assert_eq!(s_back.velocity.raw_si(), s_integ.velocity.raw_si());
}
#[test]
fn from_inertial_with_zero_origin_is_identity() {
let s_inertial = TranslationalStateTyped::<RootInertial> {
position: Position::<RootInertial>::from_raw_si(DVec3::new(7e6, 0.0, 0.0)),
velocity: Velocity::<RootInertial>::from_raw_si(DVec3::new(0.0, 7500.0, 0.0)),
};
let o = IntegOrigin::zero();
let s_integ = TranslationalStateTyped::<IntegrationFrame>::from_inertial(s_inertial, &o);
assert_eq!(s_integ.position.raw_si(), s_inertial.position.raw_si());
assert_eq!(s_integ.velocity.raw_si(), s_inertial.velocity.raw_si());
}
use proptest::prelude::*;
fn arb_finite_f64() -> impl Strategy<Value = f64> {
proptest::num::f64::ANY.prop_filter("finite", |x| x.is_finite())
}
fn arb_dvec3() -> impl Strategy<Value = DVec3> {
(arb_finite_f64(), arb_finite_f64(), arb_finite_f64())
.prop_map(|(x, y, z)| DVec3::new(x, y, z))
}
fn arb_translational_state() -> impl Strategy<Value = TranslationalState> {
(arb_dvec3(), arb_dvec3())
.prop_map(|(position, velocity)| TranslationalState { position, velocity })
}
proptest! {
#[test]
fn round_trip_translational_untyped_typed_untyped(orig in arb_translational_state()) {
let typed = TranslationalStateTyped::<RootInertial>::from_untyped_unchecked(&orig);
prop_assert_eq!(typed.to_untyped(), orig);
}
#[test]
fn round_trip_translational_typed_untyped_typed(orig in arb_translational_state()) {
let typed = TranslationalStateTyped::<RootInertial>::from_untyped_unchecked(&orig);
let lifted = TranslationalStateTyped::<RootInertial>::from_untyped_unchecked(&typed.to_untyped());
prop_assert_eq!(lifted.to_untyped(), typed.to_untyped());
}
}
#[test]
fn to_inertial_with_nonzero_origin_adds_offset() {
let s_integ = TranslationalStateTyped::<IntegrationFrame> {
position: Position::<IntegrationFrame>::from_raw_si(DVec3::new(7e6, 0.0, 0.0)),
velocity: Velocity::<IntegrationFrame>::from_raw_si(DVec3::new(0.0, 7500.0, 0.0)),
};
let o = IntegOrigin {
position: Position::<RootInertial>::from_raw_si(DVec3::new(1.5e11, 0.0, 0.0)),
velocity: Velocity::<RootInertial>::from_raw_si(DVec3::new(0.0, 30_000.0, 0.0)),
};
let s_inertial = s_integ.to_inertial(&o);
assert_eq!(
s_inertial.position.raw_si(),
DVec3::new(1.5e11 + 7e6, 0.0, 0.0)
);
assert_eq!(
s_inertial.velocity.raw_si(),
DVec3::new(0.0, 30_000.0 + 7500.0, 0.0)
);
}
}