use core::marker::PhantomData;
use astrodyn_quantities::aliases::{Acceleration, Force, Torque};
use astrodyn_quantities::frame::{BodyFrame, Frame, RootInertial, Vehicle};
use glam::{DMat3, DVec3};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GravityAcceleration {
pub grav_accel: DVec3,
pub grav_grad: DMat3,
pub grav_pot: f64,
}
impl Default for GravityAcceleration {
fn default() -> Self {
Self {
grav_accel: DVec3::ZERO,
grav_grad: DMat3::ZERO,
grav_pot: 0.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GravityAccelerationTyped<F: Frame = RootInertial> {
pub grav_accel: Acceleration<F>,
pub grav_grad: DMat3,
pub grav_pot: f64,
}
impl<F: Frame> Default for GravityAccelerationTyped<F> {
#[inline]
fn default() -> Self {
Self {
grav_accel: Acceleration::<F>::zero(),
grav_grad: DMat3::ZERO,
grav_pot: 0.0,
}
}
}
impl<F: Frame> GravityAccelerationTyped<F> {
#[inline]
pub fn to_untyped(&self) -> GravityAcceleration {
GravityAcceleration {
grav_accel: self.grav_accel.raw_si(),
grav_grad: self.grav_grad,
grav_pot: self.grav_pot,
}
}
#[inline]
pub fn from_untyped_unchecked(g: &GravityAcceleration) -> Self {
Self {
grav_accel: Acceleration::<F>::from_raw_si(g.grav_accel),
grav_grad: g.grav_grad,
grav_pot: g.grav_pot,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TotalForce {
pub force: DVec3,
pub torque: DVec3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TotalForceTyped<V: Vehicle, F: Frame = RootInertial> {
pub force: Force<F>,
pub torque: Torque<BodyFrame<V>>,
_v: PhantomData<V>,
}
impl<V: Vehicle, F: Frame> Default for TotalForceTyped<V, F> {
#[inline]
fn default() -> Self {
Self {
force: Force::<F>::zero(),
torque: Torque::<BodyFrame<V>>::zero(),
_v: PhantomData,
}
}
}
impl<V: Vehicle, F: Frame> TotalForceTyped<V, F> {
#[inline]
pub fn to_untyped(&self) -> TotalForce {
TotalForce {
force: self.force.raw_si(),
torque: self.torque.raw_si(),
}
}
#[inline]
pub fn from_untyped_unchecked(t: &TotalForce) -> Self {
Self {
force: Force::<F>::from_raw_si(t.force),
torque: Torque::<BodyFrame<V>>::from_raw_si(t.torque),
_v: PhantomData,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct FrameDerivatives {
pub trans_accel: DVec3,
pub rot_accel: DVec3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FrameDerivativesTyped<F: Frame, V: Vehicle> {
pub trans_accel: Acceleration<F>,
pub rot_accel: astrodyn_quantities::aliases::AngularAcceleration<BodyFrame<V>>,
_v: PhantomData<V>,
}
impl<F: Frame, V: Vehicle> Default for FrameDerivativesTyped<F, V> {
#[inline]
fn default() -> Self {
Self {
trans_accel: Acceleration::<F>::zero(),
rot_accel: astrodyn_quantities::aliases::AngularAcceleration::<BodyFrame<V>>::zero(),
_v: PhantomData,
}
}
}
impl<F: Frame, V: Vehicle> FrameDerivativesTyped<F, V> {
#[inline]
pub fn to_untyped(&self) -> FrameDerivatives {
FrameDerivatives {
trans_accel: self.trans_accel.raw_si(),
rot_accel: self.rot_accel.raw_si(),
}
}
#[inline]
pub fn from_untyped_unchecked(d: &FrameDerivatives) -> Self {
Self {
trans_accel: Acceleration::<F>::from_raw_si(d.trans_accel),
rot_accel:
astrodyn_quantities::aliases::AngularAcceleration::<BodyFrame<V>>::from_raw_si(
d.rot_accel,
),
_v: PhantomData,
}
}
}
impl<F: Frame, V: Vehicle> From<FrameDerivatives> for FrameDerivativesTyped<F, V> {
#[inline]
fn from(d: FrameDerivatives) -> Self {
Self::from_untyped_unchecked(&d)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DynamicsConfig {
pub translational_dynamics: bool,
pub rotational_dynamics: bool,
pub three_dof: bool,
}
impl Default for DynamicsConfig {
fn default() -> Self {
Self {
translational_dynamics: true,
rotational_dynamics: false,
three_dof: true,
}
}
}
impl DynamicsConfig {
pub fn validate(&self) {
assert!(
!(self.three_dof && self.rotational_dynamics),
"DynamicsConfig has three_dof=true AND rotational_dynamics=true. \
In JEOD, three_dof=true prevents creation of the rotational integrator. \
Set rotational_dynamics=false when three_dof=true."
);
}
}
pub fn compute_translational_acceleration(force: DVec3, inverse_mass: f64) -> DVec3 {
force * inverse_mass
}
pub fn compute_t_inertial_struct(t_struct_body: &DMat3, t_inertial_body: &DMat3) -> DMat3 {
t_struct_body.transpose() * *t_inertial_body
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct ForceContributions {
pub aero_force_struct: DVec3,
pub aero_torque_struct: DVec3,
pub srp_force_inertial: DVec3,
pub srp_torque_struct: DVec3,
pub gravity_torque_body: DVec3,
}
pub fn collect_forces(
contributions: &ForceContributions,
t_struct_body: &DMat3,
t_inertial_body: &DMat3,
) -> TotalForce {
let t_inertial_struct = compute_t_inertial_struct(t_struct_body, t_inertial_body);
let force = t_inertial_struct.transpose() * contributions.aero_force_struct
+ contributions.srp_force_inertial;
let torque = *t_struct_body * contributions.aero_torque_struct
+ *t_struct_body * contributions.srp_torque_struct
+ contributions.gravity_torque_body;
TotalForce { force, torque }
}
pub fn compute_translational_derivatives(
non_grav_force: DVec3,
inverse_mass: f64,
grav_accel: DVec3,
) -> FrameDerivatives {
let non_grav_accel = if non_grav_force == DVec3::ZERO {
DVec3::ZERO
} else {
compute_translational_acceleration(non_grav_force, inverse_mass)
};
FrameDerivatives {
trans_accel: non_grav_accel + grav_accel,
rot_accel: DVec3::ZERO,
}
}
pub fn compute_frame_derivatives(
total_force: &TotalForce,
inverse_mass: f64,
grav_accel: DVec3,
inertia: &DMat3,
inverse_inertia: &DMat3,
ang_vel_body: DVec3,
) -> FrameDerivatives {
let non_grav_accel = if total_force.force == DVec3::ZERO {
DVec3::ZERO
} else {
total_force.force * inverse_mass
};
FrameDerivatives {
trans_accel: non_grav_accel + grav_accel,
rot_accel: crate::rotational::compute_rotational_acceleration(
inertia,
inverse_inertia,
ang_vel_body,
total_force.torque,
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_gravity_acceleration() {
let ga = GravityAcceleration::default();
assert_eq!(ga.grav_accel, DVec3::ZERO);
assert_eq!(ga.grav_grad, DMat3::ZERO);
assert_eq!(ga.grav_pot, 0.0);
}
#[test]
fn default_total_force() {
let tf = TotalForce::default();
assert_eq!(tf.force, DVec3::ZERO);
assert_eq!(tf.torque, DVec3::ZERO);
}
#[test]
fn default_frame_derivatives() {
let fd = FrameDerivatives::default();
assert_eq!(fd.trans_accel, DVec3::ZERO);
assert_eq!(fd.rot_accel, DVec3::ZERO);
}
#[test]
fn default_dynamics_config() {
let dc = DynamicsConfig::default();
assert!(dc.translational_dynamics);
assert!(!dc.rotational_dynamics);
assert!(dc.three_dof);
}
#[test]
fn translational_acceleration_basic() {
let force = DVec3::new(10.0, 20.0, 30.0);
let inverse_mass = 1.0 / 5.0;
let accel = compute_translational_acceleration(force, inverse_mass);
assert!((accel - DVec3::new(2.0, 4.0, 6.0)).length() < 1e-15);
}
#[test]
fn translational_acceleration_unit_mass() {
let force = DVec3::new(3.0, -1.0, 7.0);
let accel = compute_translational_acceleration(force, 1.0);
assert_eq!(accel, force);
}
#[test]
fn validate_default_config() {
DynamicsConfig::default().validate(); }
#[test]
fn validate_six_dof_config() {
let dc = DynamicsConfig {
translational_dynamics: true,
rotational_dynamics: true,
three_dof: false,
};
dc.validate(); }
#[test]
#[should_panic(expected = "three_dof=true AND rotational_dynamics=true")]
fn validate_rejects_three_dof_with_rotational() {
let dc = DynamicsConfig {
translational_dynamics: true,
rotational_dynamics: true,
three_dof: true,
};
dc.validate();
}
#[test]
fn collect_forces_identity_frames() {
let c = ForceContributions {
aero_force_struct: DVec3::new(1.0, 0.0, 0.0),
aero_torque_struct: DVec3::new(0.0, 0.0, 5.0),
srp_force_inertial: DVec3::new(0.0, 2.0, 0.0),
srp_torque_struct: DVec3::new(0.0, 3.0, 0.0),
gravity_torque_body: DVec3::new(0.0, 0.0, 1.0),
};
let result = collect_forces(&c, &DMat3::IDENTITY, &DMat3::IDENTITY);
assert_eq!(result.force, DVec3::new(1.0, 2.0, 0.0));
assert_eq!(result.torque, DVec3::new(0.0, 3.0, 6.0));
}
#[test]
fn collect_forces_zero_contributions() {
let c = ForceContributions::default();
let result = collect_forces(&c, &DMat3::IDENTITY, &DMat3::IDENTITY);
assert_eq!(result.force, DVec3::ZERO);
assert_eq!(result.torque, DVec3::ZERO);
}
#[test]
fn collect_forces_rotated_frame() {
let rot_z90 = DMat3::from_cols(
DVec3::new(0.0, 1.0, 0.0),
DVec3::new(-1.0, 0.0, 0.0),
DVec3::new(0.0, 0.0, 1.0),
);
let c = ForceContributions {
aero_force_struct: DVec3::new(10.0, 0.0, 0.0),
..Default::default()
};
let result = collect_forces(&c, &DMat3::IDENTITY, &rot_z90);
let expected_force = rot_z90.transpose() * DVec3::new(10.0, 0.0, 0.0);
assert!((result.force - expected_force).length() < 1e-12);
}
#[test]
fn frame_derivatives_gravity_only() {
let total_force = TotalForce::default(); let grav = DVec3::new(0.0, 0.0, -9.81);
let inertia = DMat3::from_diagonal(DVec3::new(10.0, 20.0, 30.0));
let inv_inertia = DMat3::from_diagonal(DVec3::new(0.1, 0.05, 1.0 / 30.0));
let fd = compute_frame_derivatives(
&total_force,
1.0 / 100.0,
grav,
&inertia,
&inv_inertia,
DVec3::ZERO,
);
assert_eq!(fd.trans_accel, grav);
assert!(fd.rot_accel.length() < 1e-20);
}
#[test]
fn frame_derivatives_with_force() {
let total_force = TotalForce {
force: DVec3::new(100.0, 0.0, 0.0),
torque: DVec3::ZERO,
};
let grav = DVec3::new(0.0, 0.0, -9.81);
let inertia = DMat3::from_diagonal(DVec3::splat(10.0));
let inv_inertia = DMat3::from_diagonal(DVec3::splat(0.1));
let fd = compute_frame_derivatives(
&total_force,
1.0 / 50.0,
grav,
&inertia,
&inv_inertia,
DVec3::ZERO,
);
assert!((fd.trans_accel - DVec3::new(2.0, 0.0, -9.81)).length() < 1e-12);
}
#[test]
fn typed_frame_derivatives_round_trip() {
use astrodyn_quantities::frame::{RootInertial, TestVehicle};
let untyped = FrameDerivatives {
trans_accel: DVec3::new(1.0, 2.0, 3.0),
rot_accel: DVec3::new(0.1, 0.2, 0.3),
};
let typed =
FrameDerivativesTyped::<RootInertial, TestVehicle>::from_untyped_unchecked(&untyped);
let back = typed.to_untyped();
assert_eq!(back, untyped);
}
#[test]
fn typed_total_force_round_trip() {
use astrodyn_quantities::frame::TestVehicle;
let untyped = TotalForce {
force: DVec3::new(100.0, 0.0, 0.0),
torque: DVec3::new(0.0, 0.5, 0.0),
};
let typed = TotalForceTyped::<TestVehicle>::from_untyped_unchecked(&untyped);
let back = typed.to_untyped();
assert_eq!(back, untyped);
}
#[test]
fn typed_total_force_default_is_zero() {
use astrodyn_quantities::frame::TestVehicle;
let typed = TotalForceTyped::<TestVehicle>::default();
let back = typed.to_untyped();
assert_eq!(back, TotalForce::default());
}
#[test]
fn typed_gravity_acceleration_round_trip() {
use astrodyn_quantities::frame::RootInertial;
let untyped = GravityAcceleration {
grav_accel: DVec3::new(0.0, 0.0, -9.81),
grav_grad: DMat3::IDENTITY * 1e-6,
grav_pot: 6.25e7,
};
let typed = GravityAccelerationTyped::<RootInertial>::from_untyped_unchecked(&untyped);
let back = typed.to_untyped();
assert_eq!(back, untyped);
}
use astrodyn_quantities::frame::{RootInertial, TestVehicle};
use proptest::prelude::*;
fn arb_finite_bounded() -> impl Strategy<Value = f64> {
prop_oneof![
(1.0e-9_f64..1.0e9_f64),
(1.0e-9_f64..1.0e9_f64).prop_map(|x| -x),
]
}
fn arb_dvec3() -> impl Strategy<Value = DVec3> {
(
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
)
.prop_map(|(x, y, z)| DVec3::new(x, y, z))
}
fn arb_dmat3() -> impl Strategy<Value = DMat3> {
(
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
arb_finite_bounded(),
)
.prop_map(|(a, b, c, d, e, f, g, h, i)| {
DMat3::from_cols(
DVec3::new(a, b, c),
DVec3::new(d, e, f),
DVec3::new(g, h, i),
)
})
}
fn arb_total_force() -> impl Strategy<Value = TotalForce> {
(arb_dvec3(), arb_dvec3()).prop_map(|(force, torque)| TotalForce { force, torque })
}
fn arb_frame_derivatives() -> impl Strategy<Value = FrameDerivatives> {
(arb_dvec3(), arb_dvec3()).prop_map(|(trans_accel, rot_accel)| FrameDerivatives {
trans_accel,
rot_accel,
})
}
fn arb_gravity_acceleration() -> impl Strategy<Value = GravityAcceleration> {
(arb_dvec3(), arb_dmat3(), arb_finite_bounded()).prop_map(
|(grav_accel, grav_grad, grav_pot)| GravityAcceleration {
grav_accel,
grav_grad,
grav_pot,
},
)
}
proptest! {
#[test]
fn round_trip_total_force_untyped_typed_untyped(orig in arb_total_force()) {
let typed = TotalForceTyped::<TestVehicle, RootInertial>::from_untyped_unchecked(&orig);
prop_assert_eq!(typed.to_untyped(), orig);
}
#[test]
fn round_trip_total_force_typed_untyped_typed(orig in arb_total_force()) {
let typed = TotalForceTyped::<TestVehicle, RootInertial>::from_untyped_unchecked(&orig);
let lifted = TotalForceTyped::<TestVehicle, RootInertial>::from_untyped_unchecked(&typed.to_untyped());
prop_assert_eq!(lifted.to_untyped(), typed.to_untyped());
}
#[test]
fn round_trip_frame_derivatives_untyped_typed_untyped(orig in arb_frame_derivatives()) {
let typed = FrameDerivativesTyped::<RootInertial, TestVehicle>::from_untyped_unchecked(&orig);
prop_assert_eq!(typed.to_untyped(), orig);
}
#[test]
fn round_trip_frame_derivatives_typed_untyped_typed(orig in arb_frame_derivatives()) {
let typed = FrameDerivativesTyped::<RootInertial, TestVehicle>::from_untyped_unchecked(&orig);
let lifted = FrameDerivativesTyped::<RootInertial, TestVehicle>::from_untyped_unchecked(&typed.to_untyped());
prop_assert_eq!(lifted.to_untyped(), typed.to_untyped());
}
#[test]
fn round_trip_gravity_acceleration_untyped_typed_untyped(orig in arb_gravity_acceleration()) {
let typed = GravityAccelerationTyped::<RootInertial>::from_untyped_unchecked(&orig);
prop_assert_eq!(typed.to_untyped(), orig);
}
#[test]
fn round_trip_gravity_acceleration_typed_untyped_typed(orig in arb_gravity_acceleration()) {
let typed = GravityAccelerationTyped::<RootInertial>::from_untyped_unchecked(&orig);
let lifted = GravityAccelerationTyped::<RootInertial>::from_untyped_unchecked(&typed.to_untyped());
prop_assert_eq!(lifted.to_untyped(), typed.to_untyped());
}
}
}