use astrodyn_quantities::dims::SpecificAngMomDim;
use astrodyn_quantities::prelude::{Position, Qty3, RootInertial};
use glam::DVec3;
use uom::si::angle::radian;
use uom::si::f64::Angle;
pub(crate) fn solar_beta_angle_impl(orbit_ang_momentum: DVec3, sun_direction: DVec3) -> f64 {
assert!(
orbit_ang_momentum.length_squared() > 0.0,
"orbit_ang_momentum must be non-zero"
);
assert!(
sun_direction.length_squared() > 0.0,
"sun_direction must be non-zero"
);
let h_hat = orbit_ang_momentum.normalize();
let s_hat = sun_direction.normalize();
let dot = h_hat.dot(s_hat).clamp(-1.0, 1.0);
dot.asin()
}
pub fn solar_beta_angle_typed(
orbit_ang_momentum: Qty3<SpecificAngMomDim, RootInertial>,
sun_direction: Position<RootInertial>,
) -> Angle {
let beta = solar_beta_angle_impl(orbit_ang_momentum.raw_si(), sun_direction.raw_si());
Angle::new::<radian>(beta)
}
pub fn compute_body_solar_beta(position: DVec3, velocity: DVec3, sun_position: DVec3) -> f64 {
let h = position.cross(velocity);
let rel_sun = sun_position - position;
assert!(
h.length_squared() > 0.0,
"compute_body_solar_beta: orbital angular momentum is zero; \
solar beta angle is undefined"
);
assert!(
rel_sun.length_squared() > 0.0,
"compute_body_solar_beta: sun_position coincides with position; \
solar beta angle is undefined"
);
let h_typed = Qty3::<SpecificAngMomDim, RootInertial>::from_raw_si(h);
let sun_typed = Position::<RootInertial>::from_raw_si(rel_sun);
solar_beta_angle_typed(h_typed, sun_typed).get::<radian>()
}
pub fn compute_body_solar_beta_typed(
position: Position<RootInertial>,
velocity: astrodyn_quantities::aliases::Velocity<RootInertial>,
sun_position: Position<RootInertial>,
) -> Angle {
let pos = position.raw_si();
let vel = velocity.raw_si();
let sun = sun_position.raw_si();
let h = pos.cross(vel);
let rel_sun = sun - pos;
assert!(
h.length_squared() > 0.0,
"compute_body_solar_beta_typed: orbital angular momentum is zero; \
solar beta angle is undefined"
);
assert!(
rel_sun.length_squared() > 0.0,
"compute_body_solar_beta_typed: sun_position coincides with position; \
solar beta angle is undefined"
);
let h_typed = Qty3::<SpecificAngMomDim, RootInertial>::from_raw_si(h);
let sun_typed = Position::<RootInertial>::from_raw_si(rel_sun);
solar_beta_angle_typed(h_typed, sun_typed)
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_quantities::prelude::Vec3Ext;
use std::f64::consts::PI;
use solar_beta_angle_impl as solar_beta_angle;
#[test]
fn sun_in_orbit_plane() {
let h = DVec3::new(0.0, 0.0, 1.0); let sun = DVec3::new(1.0, 0.0, 0.0); let beta = solar_beta_angle(h, sun);
assert!(beta.abs() < 1e-15);
}
#[test]
fn sun_perpendicular_to_orbit_plane() {
let h = DVec3::new(0.0, 0.0, 1.0);
let sun = DVec3::new(0.0, 0.0, 1.0);
let beta = solar_beta_angle(h, sun);
assert!((beta - PI / 2.0).abs() < 1e-15);
}
#[test]
fn sun_opposite_to_orbit_normal() {
let h = DVec3::new(0.0, 0.0, 1.0);
let sun = DVec3::new(0.0, 0.0, -1.0);
let beta = solar_beta_angle(h, sun);
assert!((beta + PI / 2.0).abs() < 1e-15);
}
#[test]
fn forty_five_degree_beta() {
let h = DVec3::new(0.0, 0.0, 1.0);
let sun = DVec3::new(1.0, 0.0, 1.0); let beta = solar_beta_angle(h, sun);
assert!((beta - PI / 4.0).abs() < 1e-14);
}
#[test]
fn unnormalized_inputs() {
let h = DVec3::new(0.0, 0.0, 1e10);
let sun = DVec3::new(1e8, 0.0, 0.0);
let beta = solar_beta_angle(h, sun);
assert!(beta.abs() < 1e-14);
}
#[test]
fn iss_like_orbit() {
let inc = 51.6_f64.to_radians();
let h = DVec3::new(0.0, -inc.sin(), inc.cos());
let sun = DVec3::new(1.0, 0.0, 0.0);
let beta = solar_beta_angle(h, sun);
assert!(beta.abs() < 1e-14);
}
fn h_in<F: astrodyn_quantities::frame::Frame>(v: DVec3) -> Qty3<SpecificAngMomDim, F> {
Qty3::from_raw_si(v)
}
#[test]
fn typed_bounded_in_pi_over_two() {
let inc = 51.6_f64.to_radians();
let h = h_in::<RootInertial>(DVec3::new(0.0, -inc.sin(), inc.cos()));
for (sx, sy, sz) in [
(1.0, 0.0, 0.0),
(0.0, 1.0, 0.0),
(0.0, 0.0, 1.0),
(1.0, 1.0, 1.0),
(-1.0, 2.0, -0.5),
(0.0, 0.0, -1.0),
] {
let sun = DVec3::new(sx, sy, sz).m_at::<RootInertial>();
let beta = solar_beta_angle_typed(h, sun);
let rad = beta.get::<radian>();
assert!(
rad.abs() <= PI / 2.0 + 1e-15,
"beta out of range: {rad} for sun=({sx},{sy},{sz})"
);
}
}
#[test]
fn typed_iss_like_orbit() {
let inc = 51.6_f64.to_radians();
let h_raw = DVec3::new(0.0, -inc.sin(), inc.cos());
let sun_raw = DVec3::new(1.0, 0.0, 0.0);
let beta =
solar_beta_angle_typed(h_in::<RootInertial>(h_raw), sun_raw.m_at::<RootInertial>());
assert!(beta.get::<radian>().abs() < 1e-14);
}
#[test]
fn typed_bit_identical_to_f64() {
let cases: &[(DVec3, DVec3)] = &[
(DVec3::new(0.0, 0.0, 1.0), DVec3::new(1.0, 0.0, 0.0)),
(DVec3::new(0.0, 0.0, 1.0), DVec3::new(0.0, 0.0, 1.0)),
(DVec3::new(0.0, 0.0, 1.0), DVec3::new(0.0, 0.0, -1.0)),
(DVec3::new(0.0, 0.0, 1.0), DVec3::new(1.0, 0.0, 1.0)),
(DVec3::new(0.0, 0.0, 1e10), DVec3::new(1e8, 0.0, 0.0)),
(DVec3::new(1.0, 2.0, 3.0), DVec3::new(-0.5, 0.25, 0.75)),
(DVec3::new(-1.0, 4.0, -2.0), DVec3::new(7.0, -3.0, 1.0)),
];
for &(h, sun) in cases {
let f64_result = solar_beta_angle(h, sun);
let typed_result =
solar_beta_angle_typed(h_in::<RootInertial>(h), sun.m_at::<RootInertial>());
assert_eq!(
typed_result.get::<radian>(),
f64_result,
"typed != f64 for h={h:?} sun={sun:?}"
);
}
}
}