use nalgebra::RealField;
use num_traits::Float;
use crate::types::spaces::Measurement;
pub trait ClutterModel<T: RealField, const M: usize> {
fn clutter_rate(&self) -> T;
fn clutter_density(&self, measurement: &Measurement<T, M>) -> T;
fn clutter_intensity(&self, measurement: &Measurement<T, M>) -> T {
self.clutter_rate() * self.clutter_density(measurement)
}
}
#[derive(Debug, Clone)]
pub struct UniformClutter<T: RealField, const M: usize> {
clutter_rate: T,
volume: T,
}
impl<T: RealField + Float + Copy, const M: usize> UniformClutter<T, M> {
pub fn new(clutter_rate: T, volume: T) -> Self {
assert!(volume > T::zero(), "Clutter volume must be positive");
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
Self {
clutter_rate,
volume,
}
}
pub fn from_bounds(clutter_rate: T, bounds: [(T, T); M]) -> Self {
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
for (i, (min, max)) in bounds.iter().enumerate() {
assert!(*max > *min, "Bound {} must have max > min", i);
}
let volume = bounds
.iter()
.fold(T::one(), |acc, (min, max)| acc * (*max - *min));
Self {
clutter_rate,
volume,
}
}
}
impl<T: RealField + Float + Copy, const M: usize> ClutterModel<T, M> for UniformClutter<T, M> {
fn clutter_rate(&self) -> T {
self.clutter_rate
}
fn clutter_density(&self, _measurement: &Measurement<T, M>) -> T {
T::one() / self.volume
}
}
#[derive(Debug, Clone)]
pub struct UniformClutter2D<T: RealField> {
clutter_rate: T,
x_bounds: (T, T),
y_bounds: (T, T),
area: T,
}
impl<T: RealField + Float + Copy> UniformClutter2D<T> {
pub fn new(clutter_rate: T, x_bounds: (T, T), y_bounds: (T, T)) -> Self {
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
assert!(x_bounds.1 > x_bounds.0, "x_bounds must have max > min");
assert!(y_bounds.1 > y_bounds.0, "y_bounds must have max > min");
let area = (x_bounds.1 - x_bounds.0) * (y_bounds.1 - y_bounds.0);
Self {
clutter_rate,
x_bounds,
y_bounds,
area,
}
}
pub fn contains(&self, measurement: &Measurement<T, 2>) -> bool {
let x = *measurement.index(0);
let y = *measurement.index(1);
x >= self.x_bounds.0 && x <= self.x_bounds.1 && y >= self.y_bounds.0 && y <= self.y_bounds.1
}
}
impl<T: RealField + Float + Copy> ClutterModel<T, 2> for UniformClutter2D<T> {
fn clutter_rate(&self) -> T {
self.clutter_rate
}
fn clutter_density(&self, _measurement: &Measurement<T, 2>) -> T {
T::one() / self.area
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct UniformClutter3D<T: RealField> {
clutter_rate: T,
x_bounds: (T, T),
y_bounds: (T, T),
z_bounds: (T, T),
volume: T,
}
impl<T: RealField + Float + Copy> UniformClutter3D<T> {
pub fn new(clutter_rate: T, x_bounds: (T, T), y_bounds: (T, T), z_bounds: (T, T)) -> Self {
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
assert!(x_bounds.1 > x_bounds.0, "x_bounds must have max > min");
assert!(y_bounds.1 > y_bounds.0, "y_bounds must have max > min");
assert!(z_bounds.1 > z_bounds.0, "z_bounds must have max > min");
let volume =
(x_bounds.1 - x_bounds.0) * (y_bounds.1 - y_bounds.0) * (z_bounds.1 - z_bounds.0);
Self {
clutter_rate,
x_bounds,
y_bounds,
z_bounds,
volume,
}
}
}
impl<T: RealField + Float + Copy> ClutterModel<T, 3> for UniformClutter3D<T> {
fn clutter_rate(&self) -> T {
self.clutter_rate
}
fn clutter_density(&self, _measurement: &Measurement<T, 3>) -> T {
T::one() / self.volume
}
}
#[derive(Debug, Clone)]
pub struct UniformClutterRangeBearing<T: RealField> {
clutter_rate: T,
range_bounds: (T, T),
bearing_bounds: (T, T),
area: T,
}
impl<T: RealField + Float + Copy> UniformClutterRangeBearing<T> {
pub fn new(clutter_rate: T, range_bounds: (T, T), bearing_bounds: (T, T)) -> Self {
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
assert!(
range_bounds.0 >= T::zero(),
"Minimum range must be non-negative"
);
assert!(
range_bounds.1 > range_bounds.0,
"range_bounds must have max > min"
);
assert!(
bearing_bounds.1 > bearing_bounds.0,
"bearing_bounds must have max > min"
);
let r_min_sq = range_bounds.0 * range_bounds.0;
let r_max_sq = range_bounds.1 * range_bounds.1;
let delta_theta = bearing_bounds.1 - bearing_bounds.0;
let half = T::from_f64(0.5).unwrap();
let area = half * (r_max_sq - r_min_sq) * delta_theta;
Self {
clutter_rate,
range_bounds,
bearing_bounds,
area,
}
}
pub fn full_circle(clutter_rate: T, max_range: T) -> Self {
let pi = T::from_f64(core::f64::consts::PI).unwrap();
Self::new(clutter_rate, (T::zero(), max_range), (-pi, pi))
}
pub fn contains(&self, measurement: &Measurement<T, 2>) -> bool {
let range = *measurement.index(0);
let bearing = *measurement.index(1);
range >= self.range_bounds.0
&& range <= self.range_bounds.1
&& bearing >= self.bearing_bounds.0
&& bearing <= self.bearing_bounds.1
}
}
impl<T: RealField + Float + Copy> ClutterModel<T, 2> for UniformClutterRangeBearing<T> {
fn clutter_rate(&self) -> T {
self.clutter_rate
}
fn clutter_density(&self, _measurement: &Measurement<T, 2>) -> T {
T::one() / self.area
}
}
#[derive(Debug, Clone)]
pub struct GaussianClutter<T: RealField, const M: usize> {
clutter_rate: T,
mean: Measurement<T, M>,
covariance: crate::types::spaces::MeasurementCovariance<T, M>,
}
impl<T: RealField + Float + Copy, const M: usize> GaussianClutter<T, M> {
pub fn new(
clutter_rate: T,
mean: Measurement<T, M>,
covariance: crate::types::spaces::MeasurementCovariance<T, M>,
) -> Self {
assert!(
clutter_rate >= T::zero(),
"Clutter rate must be non-negative"
);
assert!(
covariance.determinant_cholesky().is_some(),
"Clutter covariance must be positive definite"
);
Self {
clutter_rate,
mean,
covariance,
}
}
}
impl<T: RealField + Float + Copy, const M: usize> ClutterModel<T, M> for GaussianClutter<T, M> {
fn clutter_rate(&self) -> T {
self.clutter_rate
}
fn clutter_density(&self, measurement: &Measurement<T, M>) -> T {
use crate::types::spaces::ComputeInnovation;
let diff = measurement.innovation(self.mean);
crate::types::gaussian::gaussian_likelihood(
&diff,
&crate::types::spaces::Covariance::from_matrix(*self.covariance.as_matrix()),
)
.unwrap_or(T::zero())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uniform_clutter_2d() {
let clutter = UniformClutter2D::new(10.0_f64, (0.0, 100.0), (0.0, 100.0));
assert!((clutter.clutter_rate() - 10.0).abs() < 1e-10);
let measurement = Measurement::from_array([50.0, 50.0]);
let density = clutter.clutter_density(&measurement);
assert!((density - 0.0001).abs() < 1e-10);
}
#[test]
fn test_uniform_clutter_contains() {
let clutter = UniformClutter2D::new(10.0_f64, (0.0, 100.0), (0.0, 100.0));
let inside = Measurement::from_array([50.0, 50.0]);
let outside = Measurement::from_array([150.0, 50.0]);
assert!(clutter.contains(&inside));
assert!(!clutter.contains(&outside));
}
#[test]
fn test_clutter_intensity() {
let clutter = UniformClutter2D::new(10.0_f64, (0.0, 100.0), (0.0, 100.0));
let measurement = Measurement::from_array([50.0, 50.0]);
let intensity = clutter.clutter_intensity(&measurement);
assert!((intensity - 0.001).abs() < 1e-10);
}
#[test]
fn test_uniform_clutter_range_bearing() {
use core::f64::consts::PI;
let clutter = UniformClutterRangeBearing::new(10.0_f64, (0.0, 100.0), (-PI, PI));
assert!((clutter.clutter_rate() - 10.0).abs() < 1e-10);
let expected_area = 0.5 * 10000.0 * 2.0 * PI;
let density = clutter.clutter_density(&Measurement::from_array([50.0, 0.0]));
let expected_density = 1.0 / expected_area;
assert!(
(density - expected_density).abs() < 1e-10,
"Expected density {}, got {}",
expected_density,
density
);
}
#[test]
fn test_range_bearing_clutter_contains() {
use core::f64::consts::PI;
let clutter =
UniformClutterRangeBearing::new(10.0_f64, (10.0, 100.0), (-PI / 2.0, PI / 2.0));
let inside = Measurement::from_array([50.0, 0.0]);
assert!(clutter.contains(&inside));
let too_close = Measurement::from_array([5.0, 0.0]);
assert!(!clutter.contains(&too_close));
let too_far = Measurement::from_array([150.0, 0.0]);
assert!(!clutter.contains(&too_far));
let wrong_bearing = Measurement::from_array([50.0, 2.0]); assert!(!clutter.contains(&wrong_bearing));
}
#[test]
fn test_range_bearing_clutter_full_circle() {
let clutter = UniformClutterRangeBearing::full_circle(5.0_f64, 200.0);
assert!((clutter.clutter_rate() - 5.0).abs() < 1e-10);
let inside = Measurement::from_array([100.0, 3.0]); assert!(clutter.contains(&inside));
}
}