use serde::{Deserialize, Serialize};
const SUTHERLAND_MU_REF: f64 = 1.716e-5;
const SUTHERLAND_T_REF: f64 = 273.15;
const SUTHERLAND_S: f64 = 110.4;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AeroForce {
pub lift: f64,
pub drag: f64,
pub moment: f64,
}
#[must_use]
#[inline]
pub fn lift(dynamic_pressure: f64, reference_area: f64, cl: f64) -> f64 {
dynamic_pressure * reference_area * cl
}
#[must_use]
#[inline]
pub fn drag(dynamic_pressure: f64, reference_area: f64, cd: f64) -> f64 {
dynamic_pressure * reference_area * cd
}
#[must_use]
#[inline]
pub fn reynolds_number(density: f64, velocity: f64, length: f64, dynamic_viscosity: f64) -> f64 {
if dynamic_viscosity <= 0.0 {
return 0.0;
}
density * velocity * length / dynamic_viscosity
}
#[must_use]
#[inline]
pub fn air_dynamic_viscosity(temperature_k: f64) -> f64 {
if temperature_k <= 0.0 {
return SUTHERLAND_MU_REF;
}
SUTHERLAND_MU_REF
* (temperature_k / SUTHERLAND_T_REF).powf(1.5)
* (SUTHERLAND_T_REF + SUTHERLAND_S)
/ (temperature_k + SUTHERLAND_S)
}
#[must_use]
#[inline]
pub fn compute_aero_force(
density: f64,
velocity: f64,
reference_area: f64,
cl: f64,
cd: f64,
cm: f64,
chord: f64,
) -> AeroForce {
let q = crate::atmosphere::dynamic_pressure(density, velocity);
AeroForce {
lift: q * reference_area * cl,
drag: q * reference_area * cd,
moment: q * reference_area * chord * cm,
}
}
#[must_use]
#[inline]
pub fn body_to_wind(cn: f64, ca: f64, alpha_rad: f64) -> (f64, f64) {
let cos_a = alpha_rad.cos();
let sin_a = alpha_rad.sin();
let cl = cn * cos_a - ca * sin_a;
let cd = cn * sin_a + ca * cos_a;
(cl, cd)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lift_basic() {
let l = lift(6125.0, 10.0, 0.5);
assert!(
(l - 30625.0).abs() < 1.0,
"lift should be ~30625 N, got {l}"
);
}
#[test]
fn drag_basic() {
let d = drag(6125.0, 10.0, 0.05);
assert!((d - 3062.5).abs() < 1.0);
}
#[test]
fn zero_velocity_zero_forces() {
let q = crate::atmosphere::dynamic_pressure(1.225, 0.0);
assert_eq!(lift(q, 10.0, 0.5), 0.0);
assert_eq!(drag(q, 10.0, 0.05), 0.0);
}
#[test]
fn reynolds_at_sea_level() {
let re = reynolds_number(1.225, 50.0, 1.0, 1.8e-5);
assert!(re > 3e6 && re < 4e6, "Re should be ~3.4M, got {re}");
}
#[test]
fn reynolds_increases_with_velocity() {
let re_slow = reynolds_number(1.225, 10.0, 1.0, 1.8e-5);
let re_fast = reynolds_number(1.225, 100.0, 1.0, 1.8e-5);
assert!(re_fast > re_slow);
}
#[test]
fn air_viscosity_at_sea_level() {
let mu = air_dynamic_viscosity(288.15);
assert!(
(mu - 1.79e-5).abs() < 0.1e-5,
"viscosity at sea level should be ~1.79e-5, got {mu}"
);
}
#[test]
fn viscosity_increases_with_temperature() {
let mu_cold = air_dynamic_viscosity(250.0);
let mu_hot = air_dynamic_viscosity(350.0);
assert!(
mu_hot > mu_cold,
"viscosity should increase with temperature"
);
}
#[test]
fn aero_force_computation() {
let f = compute_aero_force(1.225, 100.0, 10.0, 0.5, 0.05, -0.1, 1.5);
assert!(f.lift > 0.0);
assert!(f.drag > 0.0);
assert!(f.moment < 0.0); }
#[test]
fn reynolds_zero_viscosity_guard() {
assert_eq!(reynolds_number(1.225, 50.0, 1.0, 0.0), 0.0);
assert_eq!(reynolds_number(1.225, 50.0, 1.0, -1.0), 0.0);
}
#[test]
fn viscosity_zero_temp_guard() {
let mu = air_dynamic_viscosity(0.0);
assert!((mu - 1.716e-5).abs() < 1e-8);
}
#[test]
fn viscosity_negative_temp_guard() {
let mu = air_dynamic_viscosity(-100.0);
assert!((mu - 1.716e-5).abs() < 1e-8);
}
#[test]
fn compute_aero_force_moment_value() {
let f = compute_aero_force(1.225, 100.0, 10.0, 0.5, 0.05, -0.1, 1.5);
assert!(
(f.moment - (-9187.5)).abs() < 1.0,
"moment should be ~-9187.5, got {}",
f.moment
);
}
#[test]
fn compute_aero_force_zero_velocity() {
let f = compute_aero_force(1.225, 0.0, 10.0, 0.5, 0.05, -0.1, 1.5);
assert_eq!(f.lift, 0.0);
assert_eq!(f.drag, 0.0);
assert_eq!(f.moment, 0.0);
}
#[test]
fn lift_scales_linearly_with_area() {
let l1 = lift(6125.0, 10.0, 0.5);
let l2 = lift(6125.0, 20.0, 0.5);
assert!(
(l2 / l1 - 2.0).abs() < 1e-10,
"lift should scale linearly with area"
);
}
#[test]
fn viscosity_at_high_altitude_temp() {
let mu_sl = air_dynamic_viscosity(288.15);
let mu_tropo = air_dynamic_viscosity(216.65);
assert!(
mu_tropo < mu_sl,
"viscosity should be lower at lower temperature"
);
}
}