use super::DecodedBds;
pub const DENSITY_THRESHOLD: f64 = -3.0;
#[inline]
fn gauss(value: f64, mu: f64, sigma: f64) -> f64 {
let z = (value - mu) / sigma;
-0.5 * z * z
}
#[inline]
fn laplace(value: f64, median: f64, b: f64) -> f64 {
if b <= 0.0 {
return 0.0;
}
-(value - median).abs() / b
}
struct Acc {
total: f64,
count: u32,
}
impl Acc {
#[inline]
fn new() -> Self {
Self {
total: 0.0,
count: 0,
}
}
#[inline]
fn add(&mut self, ld: f64) {
self.total += ld;
self.count += 1;
}
#[inline]
fn mean(&self) -> Option<f64> {
if self.count == 0 {
None
} else {
Some(self.total / f64::from(self.count))
}
}
}
#[must_use]
pub fn density_score(decoded: &DecodedBds) -> Option<f64> {
let mut acc = Acc::new();
match decoded {
DecodedBds::Bds05(p) => {
if let Some(alt) = p.alt {
acc.add(gauss(f64::from(alt), 26302.0, 10573.0));
}
}
DecodedBds::Bds40(s) => {
if let Some(v) = s.barometric_setting {
acc.add(laplace(v, 1013.2, 5.4));
}
if let Some(v) = s.selected_altitude_mcp {
acc.add(gauss(f64::from(v), 24688.0, 10844.0));
}
if let Some(v) = s.selected_altitude_fms {
acc.add(gauss(f64::from(v), 24688.0, 10844.0));
}
}
DecodedBds::Bds50(t) => {
if let Some(v) = t.roll_angle {
acc.add(laplace(v, 0.0, 10.0));
}
if let Some(v) = t.track_rate {
acc.add(laplace(v, 0.0, 0.70));
}
if let Some(v) = t.true_airspeed {
acc.add(gauss(f64::from(v), 413.0, 120.0));
}
if let Some(v) = t.groundspeed {
acc.add(gauss(f64::from(v), 418.0, 129.0));
}
}
DecodedBds::Bds60(h) => {
if let Some(v) = h.indicated_airspeed {
acc.add(gauss(f64::from(v), 272.0, 55.0));
}
if let Some(v) = h.mach_number {
acc.add(gauss(v, 0.69, 0.21));
}
if let Some(v) = h.inertial_vertical_velocity {
acc.add(gauss(f64::from(v), -298.0, 1669.0));
}
}
DecodedBds::Bds44(m) => {
acc.add(laplace(m.temperature, -21.0, 9.90));
if let Some(v) = m.wind_speed {
acc.add(gauss(f64::from(v), 52.0, 29.0));
}
if let Some(v) = m.pressure {
acc.add(laplace(f64::from(v), 393.0, 290.0));
}
}
DecodedBds::Bds45(m) => {
if let Some(v) = m.static_temperature {
acc.add(gauss(v, -14.2, 16.4));
}
if let Some(v) = m.static_pressure {
acc.add(laplace(f64::from(v), 393.0, 290.0));
}
}
_ => {}
}
acc.mean()
}
#[must_use]
pub fn passes(decoded: &DecodedBds) -> bool {
match density_score(decoded) {
Some(score) => score >= DENSITY_THRESHOLD,
None => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cruise_bds50_passes() {
let mut acc = Acc::new();
acc.add(laplace(-2.0, 0.0, 10.0));
acc.add(laplace(0.0, 0.0, 0.70));
acc.add(gauss(460.0, 413.0, 120.0));
acc.add(gauss(470.0, 418.0, 129.0));
let m = acc.mean().unwrap();
assert!(m > DENSITY_THRESHOLD, "cruise mean = {m}");
}
#[test]
fn slow_bds60_fails() {
let mut acc = Acc::new();
acc.add(gauss(50.0, 272.0, 55.0));
acc.add(gauss(0.05, 0.69, 0.21));
let m = acc.mean().unwrap();
assert!(m < DENSITY_THRESHOLD, "slow mean = {m}");
}
#[test]
fn density_at_centre_is_zero() {
assert_eq!(gauss(1.0, 1.0, 2.0), 0.0);
assert_eq!(laplace(1013.2, 1013.2, 5.4), 0.0);
assert_eq!(laplace(1.0, 0.0, 0.0), 0.0);
}
}