use crate::nrlmsise::nrlmsise;
use crate::ITRFCoord;
use crate::Instant;
use crate::mathtypes::*;
fn omega_earth() -> Vector3 {
numeris::vector![0.0, 0.0, crate::consts::OMEGA_EARTH]
}
fn omega_earth_matrix() -> Matrix3 {
Matrix3::new([
[0.0, -crate::consts::OMEGA_EARTH, 0.0],
[crate::consts::OMEGA_EARTH, 0.0, 0.0],
[0.0, 0.0, 0.0],
])
}
pub fn drag_force(
pos_gcrf: &Vector3,
pos_itrf: &Vector3,
vel_gcrf: &Vector3,
time: &crate::Instant,
cd_a_over_m: f64,
use_spaceweather: bool,
) -> Vector3 {
let itrf = ITRFCoord::from_vector(pos_itrf);
let (lat, lon, hae) = itrf.to_geodetic_rad();
let (density, _temperature) = nrlmsise(
hae / 1.0e3,
Some(lat),
Some(lon),
Some(time),
use_spaceweather,
);
let vrel = vel_gcrf - omega_earth().cross(pos_gcrf);
-0.5 * cd_a_over_m * density * vrel * vrel.norm()
}
fn compute_rho_drhodr(
pgcrf: &Vector3,
qgcrf2itrf: &Quaternion,
time: &Instant,
use_spaceweather: bool,
) -> (f64, Vector3) {
let pitrf = qgcrf2itrf * pgcrf;
let itrf = ITRFCoord::from(pitrf);
let hae = itrf.hae();
let lat = itrf.latitude_rad();
let lon = itrf.longitude_rad();
let (rho0, _) = nrlmsise(hae / 1.0e3, Some(lat), Some(lon), Some(time), use_spaceweather);
let dh = 100.0; let (rho1, _) = nrlmsise(
(hae + dh) / 1.0e3,
Some(lat),
Some(lon),
Some(time),
use_spaceweather,
);
let drho_dh = (rho1 - rho0) / dh;
let up_itrf = itrf.q_ned2itrf() * numeris::vector![0.0, 0.0, -1.0];
let up_gcrf = qgcrf2itrf.conjugate() * up_itrf;
(rho0, drho_dh * up_gcrf)
}
pub fn drag_and_partials(
pos_gcrf: &Vector3,
qgcrf2itrf: &Quaternion,
vel_gcrf: &Vector3,
time: &crate::Instant,
cd_a_over_m: f64,
use_spaceweather: bool,
) -> (Vector3, Matrix3, Matrix3) {
let (density, drhodr) = compute_rho_drhodr(pos_gcrf, qgcrf2itrf, time, use_spaceweather);
let vrel = vel_gcrf - omega_earth().cross(pos_gcrf);
let vrel_norm = vrel.norm();
let drag_accel_gcrf = -0.5 * cd_a_over_m * density * vrel * vrel_norm;
let dacceldv = -0.5
* cd_a_over_m
* density
* (vrel * vrel.transpose() / vrel_norm + vrel_norm * Matrix3::eye());
let dacceldr = -0.5 * cd_a_over_m * vrel * vrel_norm * drhodr.transpose()
- dacceldv * omega_earth_matrix();
(drag_accel_gcrf, dacceldr, dacceldv)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drag_force_direction() {
let r = 6778.0e3; let v_circ = (crate::consts::MU_EARTH / r).sqrt();
let pos_gcrf = numeris::vector![r, 0.0, 0.0];
let pos_itrf = pos_gcrf; let vel_gcrf = numeris::vector![0.0, v_circ, 0.0]; let time = Instant::from_datetime(2020, 1, 1, 0, 0, 0.0).unwrap();
let cd_a_over_m = 0.01;
let drag = drag_force(&pos_gcrf, &pos_itrf, &vel_gcrf, &time, cd_a_over_m, false);
let vrel = vel_gcrf - omega_earth().cross(&pos_gcrf);
let drag_dot_vrel = drag.dot(&vrel);
assert!(
drag_dot_vrel < 0.0,
"Drag should oppose velocity, dot product = {}",
drag_dot_vrel
);
assert!(drag.norm() > 0.0, "Drag magnitude should be > 0");
let drag2 = drag_force(
&pos_gcrf, &pos_itrf, &vel_gcrf, &time,
cd_a_over_m * 2.0, false,
);
assert!((drag2.norm() - drag.norm() * 2.0).abs() < 1.0e-10 * drag.norm());
}
#[test]
fn test_drag_force_zero_at_high_alt() {
let r = 8378.0e3; let v_circ = (crate::consts::MU_EARTH / r).sqrt();
let pos_gcrf = numeris::vector![r, 0.0, 0.0];
let pos_itrf = pos_gcrf;
let vel_gcrf = numeris::vector![0.0, v_circ, 0.0];
let time = Instant::from_datetime(2020, 1, 1, 0, 0, 0.0).unwrap();
let drag = drag_force(&pos_gcrf, &pos_itrf, &vel_gcrf, &time, 0.01, false);
assert!(
drag.norm() < 1.0e-10,
"Drag at 2000 km = {:.3e}, expected < 1e-10",
drag.norm()
);
}
}