use nalgebra::RealField;
use num_traits::Float;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::types::gaussian::GaussianState;
use crate::types::spaces::{Measurement, StateCovariance, StateVector};
pub trait BirthModel<T: RealField, const N: usize> {
#[cfg(feature = "alloc")]
#[deprecated(
since = "0.4.0",
note = "Use birth_components_vec() instead, which supports dynamic generation"
)]
fn birth_components(&self) -> &[GaussianState<T, N>] {
&[]
}
#[cfg(feature = "alloc")]
fn birth_components_vec(&self) -> Vec<GaussianState<T, N>> {
#[allow(deprecated)]
self.birth_components().to_vec()
}
fn total_birth_mass(&self) -> T;
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct FixedBirthModel<T: RealField, const N: usize> {
components: Vec<GaussianState<T, N>>,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Copy, const N: usize> FixedBirthModel<T, N> {
pub fn new() -> Self {
Self {
components: Vec::new(),
}
}
pub fn from_components(components: Vec<GaussianState<T, N>>) -> Self {
Self { components }
}
pub fn add_component(&mut self, component: GaussianState<T, N>) {
self.components.push(component);
}
pub fn add_birth_location(
&mut self,
weight: T,
mean: StateVector<T, N>,
covariance: StateCovariance<T, N>,
) {
assert!(weight >= T::zero(), "Birth weight must be non-negative");
assert!(
covariance.determinant_cholesky().is_some(),
"Birth covariance must be positive definite"
);
self.components
.push(GaussianState::new(weight, mean, covariance));
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Copy, const N: usize> Default for FixedBirthModel<T, N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Copy, const N: usize> BirthModel<T, N> for FixedBirthModel<T, N> {
#[allow(deprecated)]
fn birth_components(&self) -> &[GaussianState<T, N>] {
&self.components
}
fn birth_components_vec(&self) -> Vec<GaussianState<T, N>> {
self.components.clone()
}
fn total_birth_mass(&self) -> T {
self.components
.iter()
.fold(T::zero(), |acc, c| acc + c.weight)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct AdaptiveBirthModel<T: RealField, const N: usize, const M: usize> {
birth_weight: T,
velocity_covariance: T,
base_components: Vec<GaussianState<T, N>>,
_marker: core::marker::PhantomData<[T; M]>,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> AdaptiveBirthModel<T, 4, 2> {
pub fn new_cv2d(birth_weight: T, velocity_covariance: T) -> Self {
assert!(
birth_weight >= T::zero(),
"Birth weight must be non-negative"
);
assert!(
velocity_covariance > T::zero(),
"Velocity covariance must be positive"
);
Self {
birth_weight,
velocity_covariance,
base_components: Vec::new(),
_marker: core::marker::PhantomData,
}
}
pub fn add_base_component(&mut self, component: GaussianState<T, 4>) {
self.base_components.push(component);
}
pub fn birth_from_measurements(
&self,
measurements: &[Measurement<T, 2>],
) -> Vec<GaussianState<T, 4>> {
let mut components = self.base_components.clone();
for meas in measurements {
let mean =
StateVector::from_array([*meas.index(0), *meas.index(1), T::zero(), T::zero()]);
let zero = T::zero();
let pos_cov = T::one(); let vel_cov = self.velocity_covariance;
let covariance = StateCovariance::from_matrix(nalgebra::matrix![
pos_cov, zero, zero, zero;
zero, pos_cov, zero, zero;
zero, zero, vel_cov, zero;
zero, zero, zero, vel_cov
]);
components.push(GaussianState::new(self.birth_weight, mean, covariance));
}
components
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> BirthModel<T, 4> for AdaptiveBirthModel<T, 4, 2> {
#[allow(deprecated)]
fn birth_components(&self) -> &[GaussianState<T, 4>] {
&self.base_components
}
fn birth_components_vec(&self) -> Vec<GaussianState<T, 4>> {
self.base_components.clone()
}
fn total_birth_mass(&self) -> T {
self.base_components
.iter()
.fold(T::zero(), |acc, c| acc + c.weight)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct UniformBirthModel2D<T: RealField> {
total_intensity: T,
components: Vec<GaussianState<T, 4>>,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> UniformBirthModel2D<T> {
pub fn new(
total_intensity: T,
x_bounds: (T, T),
y_bounds: (T, T),
grid_size: usize,
position_std: T,
velocity_std: T,
) -> Self {
assert!(
total_intensity >= T::zero(),
"Total intensity 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!(grid_size >= 2, "Grid size must be at least 2");
assert!(position_std > T::zero(), "Position std must be positive");
assert!(velocity_std > T::zero(), "Velocity std must be positive");
let mut components = Vec::with_capacity(grid_size * grid_size);
let n = T::from_usize(grid_size).unwrap();
let n_sq = T::from_usize(grid_size * grid_size).unwrap();
let weight_per_component = total_intensity / n_sq;
let dx = (x_bounds.1 - x_bounds.0) / (n - T::one());
let dy = (y_bounds.1 - y_bounds.0) / (n - T::one());
let pos_var = position_std * position_std;
let vel_var = velocity_std * velocity_std;
let zero = T::zero();
for i in 0..grid_size {
for j in 0..grid_size {
let x = x_bounds.0 + T::from_usize(i).unwrap() * dx;
let y = y_bounds.0 + T::from_usize(j).unwrap() * dy;
let mean = StateVector::from_array([x, y, zero, zero]);
let covariance = StateCovariance::from_matrix(nalgebra::matrix![
pos_var, zero, zero, zero;
zero, pos_var, zero, zero;
zero, zero, vel_var, zero;
zero, zero, zero, vel_var
]);
components.push(GaussianState::new(weight_per_component, mean, covariance));
}
}
Self {
total_intensity,
components,
}
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Copy> BirthModel<T, 4> for UniformBirthModel2D<T> {
#[allow(deprecated)]
fn birth_components(&self) -> &[GaussianState<T, 4>] {
&self.components
}
fn birth_components_vec(&self) -> Vec<GaussianState<T, 4>> {
self.components.clone()
}
fn total_birth_mass(&self) -> T {
self.total_intensity
}
}
pub trait MeasurementStateExpander<T: RealField, const N: usize, const M: usize> {
fn expand_measurement(
&self,
measurement: &Measurement<T, M>,
) -> (StateVector<T, N>, StateCovariance<T, N>);
fn birth_weight(&self) -> T;
fn birth_existence(&self) -> T {
self.birth_weight()
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct ConstantVelocity2DExpander<T: RealField> {
pub position_covariance: T,
pub velocity_covariance: T,
pub birth_weight: T,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> ConstantVelocity2DExpander<T> {
pub fn new(position_covariance: T, velocity_covariance: T, birth_weight: T) -> Self {
assert!(
position_covariance > T::zero(),
"Position covariance must be positive"
);
assert!(
velocity_covariance > T::zero(),
"Velocity covariance must be positive"
);
assert!(birth_weight > T::zero(), "Birth weight must be positive");
Self {
position_covariance,
velocity_covariance,
birth_weight,
}
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> MeasurementStateExpander<T, 4, 2>
for ConstantVelocity2DExpander<T>
{
fn expand_measurement(
&self,
measurement: &Measurement<T, 2>,
) -> (StateVector<T, 4>, StateCovariance<T, 4>) {
let zero = T::zero();
let mean =
StateVector::from_array([*measurement.index(0), *measurement.index(1), zero, zero]);
let covariance = StateCovariance::from_matrix(nalgebra::matrix![
self.position_covariance, zero, zero, zero;
zero, self.position_covariance, zero, zero;
zero, zero, self.velocity_covariance, zero;
zero, zero, zero, self.velocity_covariance
]);
(mean, covariance)
}
fn birth_weight(&self) -> T {
self.birth_weight
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct ConstantVelocity3DExpander<T: RealField> {
pub position_covariance: T,
pub velocity_covariance: T,
pub birth_weight: T,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> ConstantVelocity3DExpander<T> {
pub fn new(position_covariance: T, velocity_covariance: T, birth_weight: T) -> Self {
assert!(
position_covariance > T::zero(),
"Position covariance must be positive"
);
assert!(
velocity_covariance > T::zero(),
"Velocity covariance must be positive"
);
assert!(birth_weight > T::zero(), "Birth weight must be positive");
Self {
position_covariance,
velocity_covariance,
birth_weight,
}
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> MeasurementStateExpander<T, 6, 3>
for ConstantVelocity3DExpander<T>
{
fn expand_measurement(
&self,
measurement: &Measurement<T, 3>,
) -> (StateVector<T, 6>, StateCovariance<T, 6>) {
let zero = T::zero();
let mean = StateVector::from_array([
*measurement.index(0),
*measurement.index(1),
*measurement.index(2),
zero,
zero,
zero,
]);
let covariance = StateCovariance::from_matrix(nalgebra::matrix![
self.position_covariance, zero, zero, zero, zero, zero;
zero, self.position_covariance, zero, zero, zero, zero;
zero, zero, self.position_covariance, zero, zero, zero;
zero, zero, zero, self.velocity_covariance, zero, zero;
zero, zero, zero, zero, self.velocity_covariance, zero;
zero, zero, zero, zero, zero, self.velocity_covariance
]);
(mean, covariance)
}
fn birth_weight(&self) -> T {
self.birth_weight
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct RangeBearingToCv2DExpander<T: RealField> {
pub sensor_x: T,
pub sensor_y: T,
pub range_variance: T,
pub bearing_variance: T,
pub velocity_covariance: T,
pub birth_weight: T,
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> RangeBearingToCv2DExpander<T> {
pub fn new(
range_variance: T,
bearing_variance: T,
velocity_covariance: T,
birth_weight: T,
) -> Self {
Self::at_position(
T::zero(),
T::zero(),
range_variance,
bearing_variance,
velocity_covariance,
birth_weight,
)
}
pub fn at_position(
sensor_x: T,
sensor_y: T,
range_variance: T,
bearing_variance: T,
velocity_covariance: T,
birth_weight: T,
) -> Self {
assert!(
range_variance > T::zero(),
"Range variance must be positive"
);
assert!(
bearing_variance > T::zero(),
"Bearing variance must be positive"
);
assert!(
velocity_covariance > T::zero(),
"Velocity covariance must be positive"
);
assert!(birth_weight > T::zero(), "Birth weight must be positive");
Self {
sensor_x,
sensor_y,
range_variance,
bearing_variance,
velocity_covariance,
birth_weight,
}
}
}
#[cfg(feature = "alloc")]
impl<T: RealField + Float + Copy> MeasurementStateExpander<T, 4, 2>
for RangeBearingToCv2DExpander<T>
{
fn expand_measurement(
&self,
measurement: &Measurement<T, 2>,
) -> (StateVector<T, 4>, StateCovariance<T, 4>) {
let range = *measurement.index(0);
let bearing = *measurement.index(1);
let zero = T::zero();
let cos_b = Float::cos(bearing);
let sin_b = Float::sin(bearing);
let x = self.sensor_x + range * cos_b;
let y = self.sensor_y + range * sin_b;
let mean = StateVector::from_array([x, y, zero, zero]);
let j = nalgebra::matrix![
cos_b, -range * sin_b;
sin_b, range * cos_b
];
let polar_cov = nalgebra::matrix![
self.range_variance, zero;
zero, self.bearing_variance
];
let cart_pos_cov = j * polar_cov * j.transpose();
let covariance = StateCovariance::from_matrix(nalgebra::matrix![
cart_pos_cov[(0, 0)], cart_pos_cov[(0, 1)], zero, zero;
cart_pos_cov[(1, 0)], cart_pos_cov[(1, 1)], zero, zero;
zero, zero, self.velocity_covariance, zero;
zero, zero, zero, self.velocity_covariance
]);
(mean, covariance)
}
fn birth_weight(&self) -> T {
self.birth_weight
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct MeasurementDrivenBirthModel<T, E, const N: usize, const M: usize>
where
T: RealField,
E: MeasurementStateExpander<T, N, M>,
{
expander: E,
base_components: Vec<GaussianState<T, N>>,
adaptive_measurements: Vec<Measurement<T, M>>,
}
#[cfg(feature = "alloc")]
impl<T, E, const N: usize, const M: usize> MeasurementDrivenBirthModel<T, E, N, M>
where
T: RealField + Copy,
E: MeasurementStateExpander<T, N, M>,
{
pub fn new(expander: E) -> Self {
Self {
expander,
base_components: Vec::new(),
adaptive_measurements: Vec::new(),
}
}
pub fn with_base_components(expander: E, base_components: Vec<GaussianState<T, N>>) -> Self {
Self {
expander,
base_components,
adaptive_measurements: Vec::new(),
}
}
pub fn add_base_component(&mut self, component: GaussianState<T, N>) {
self.base_components.push(component);
}
pub fn set_measurements(&mut self, measurements: Vec<Measurement<T, M>>) {
self.adaptive_measurements = measurements;
}
pub fn set_measurements_from_slice(&mut self, measurements: &[Measurement<T, M>]) {
self.adaptive_measurements = measurements.to_vec();
}
pub fn clear_measurements(&mut self) {
self.adaptive_measurements.clear();
}
pub fn expander(&self) -> &E {
&self.expander
}
pub fn expander_mut(&mut self) -> &mut E {
&mut self.expander
}
pub fn num_adaptive_measurements(&self) -> usize {
self.adaptive_measurements.len()
}
pub fn num_base_components(&self) -> usize {
self.base_components.len()
}
}
#[cfg(feature = "alloc")]
impl<T, E, const N: usize, const M: usize> BirthModel<T, N>
for MeasurementDrivenBirthModel<T, E, N, M>
where
T: RealField + Float + Copy,
E: MeasurementStateExpander<T, N, M>,
{
fn birth_components_vec(&self) -> Vec<GaussianState<T, N>> {
let mut components = self.base_components.clone();
for measurement in &self.adaptive_measurements {
let (mean, covariance) = self.expander.expand_measurement(measurement);
components.push(GaussianState::new(
self.expander.birth_weight(),
mean,
covariance,
));
}
components
}
fn total_birth_mass(&self) -> T {
let base_mass = self
.base_components
.iter()
.fold(T::zero(), |acc, c| acc + c.weight);
let adaptive_mass =
T::from_usize(self.adaptive_measurements.len()).unwrap() * self.expander.birth_weight();
base_mass + adaptive_mass
}
}
#[cfg(feature = "alloc")]
impl<T, E, const N: usize, const M: usize> crate::filters::lmb::LabeledBirthModel<T, N>
for MeasurementDrivenBirthModel<T, E, N, M>
where
T: RealField + Float + Copy,
E: MeasurementStateExpander<T, N, M>,
{
fn birth_tracks(
&self,
label_gen: &mut crate::types::labels::LabelGenerator,
) -> Vec<crate::types::labels::BernoulliTrack<T, N>> {
use crate::types::labels::BernoulliTrack;
let mut tracks = Vec::new();
for component in &self.base_components {
let label = label_gen.next_label();
tracks.push(BernoulliTrack::new(
label,
self.expander.birth_existence(),
component.clone(),
));
}
for measurement in &self.adaptive_measurements {
let (mean, covariance) = self.expander.expand_measurement(measurement);
let label = label_gen.next_label();
let state = GaussianState::new(T::one(), mean, covariance);
tracks.push(BernoulliTrack::new(
label,
self.expander.birth_existence(),
state,
));
}
tracks
}
fn expected_birth_count(&self) -> T {
let n_base = T::from_usize(self.base_components.len()).unwrap();
let n_adaptive = T::from_usize(self.adaptive_measurements.len()).unwrap();
(n_base + n_adaptive) * self.expander.birth_existence()
}
}
#[cfg(feature = "alloc")]
pub type MeasurementDrivenBirthModelCv2D<T> =
MeasurementDrivenBirthModel<T, ConstantVelocity2DExpander<T>, 4, 2>;
#[cfg(feature = "alloc")]
pub type MeasurementDrivenBirthModelCv3D<T> =
MeasurementDrivenBirthModel<T, ConstantVelocity3DExpander<T>, 6, 3>;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
#[test]
fn test_fixed_birth_model() {
let mut birth = FixedBirthModel::<f64, 4>::new();
let mean = StateVector::from_array([0.0, 0.0, 0.0, 0.0]);
let cov = StateCovariance::identity();
birth.add_birth_location(0.1, mean, cov);
let components = birth.birth_components_vec();
assert_eq!(components.len(), 1);
assert!((birth.total_birth_mass() - 0.1).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_adaptive_birth_from_measurements() {
let birth = AdaptiveBirthModel::<f64, 4, 2>::new_cv2d(0.01, 100.0);
let measurements = [
Measurement::from_array([10.0, 20.0]),
Measurement::from_array([30.0, 40.0]),
];
let components = birth.birth_from_measurements(&measurements);
assert_eq!(components.len(), 2);
assert!((components[0].mean.index(0) - 10.0).abs() < 1e-10);
assert!((components[0].mean.index(1) - 20.0).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_uniform_birth_model() {
let birth = UniformBirthModel2D::new(
0.5_f64, (0.0, 100.0), (0.0, 100.0), 3, 10.0, 5.0, );
let components = birth.birth_components_vec();
assert_eq!(components.len(), 9);
assert!((birth.total_birth_mass() - 0.5).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_cv2d_expander() {
let expander = ConstantVelocity2DExpander::new(10.0, 100.0, 0.05);
let measurement = Measurement::from_array([15.0, 25.0]);
let (mean, cov) = expander.expand_measurement(&measurement);
assert!((mean.index(0) - 15.0).abs() < 1e-10);
assert!((mean.index(1) - 25.0).abs() < 1e-10);
assert!(mean.index(2).abs() < 1e-10);
assert!(mean.index(3).abs() < 1e-10);
assert!((cov.as_matrix()[(0, 0)] - 10.0).abs() < 1e-10);
assert!((cov.as_matrix()[(1, 1)] - 10.0).abs() < 1e-10);
assert!((cov.as_matrix()[(2, 2)] - 100.0).abs() < 1e-10);
assert!((cov.as_matrix()[(3, 3)] - 100.0).abs() < 1e-10);
assert!((expander.birth_weight() - 0.05).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_cv3d_expander() {
let expander = ConstantVelocity3DExpander::new(5.0, 50.0, 0.03);
let measurement = Measurement::from_array([1.0, 2.0, 3.0]);
let (mean, cov) = expander.expand_measurement(&measurement);
assert!((mean.index(0) - 1.0).abs() < 1e-10);
assert!((mean.index(1) - 2.0).abs() < 1e-10);
assert!((mean.index(2) - 3.0).abs() < 1e-10);
assert!(mean.index(3).abs() < 1e-10);
assert!(mean.index(4).abs() < 1e-10);
assert!(mean.index(5).abs() < 1e-10);
assert!((cov.as_matrix()[(0, 0)] - 5.0).abs() < 1e-10);
assert!((cov.as_matrix()[(3, 3)] - 50.0).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_range_bearing_expander() {
let expander = RangeBearingToCv2DExpander::new(1.0, 0.01, 100.0, 0.05);
let measurement = Measurement::from_array([10.0, 0.0]);
let (mean, _cov) = expander.expand_measurement(&measurement);
assert!((mean.index(0) - 10.0).abs() < 1e-10);
assert!(mean.index(1).abs() < 1e-10);
assert!(mean.index(2).abs() < 1e-10);
assert!(mean.index(3).abs() < 1e-10);
let measurement_y = Measurement::from_array([10.0, core::f64::consts::FRAC_PI_2]);
let (mean_y, _) = expander.expand_measurement(&measurement_y);
assert!(mean_y.index(0).abs() < 1e-10);
assert!((mean_y.index(1) - 10.0).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_measurement_driven_birth_model() {
let expander = ConstantVelocity2DExpander::new(10.0, 100.0, 0.05);
let mut birth = MeasurementDrivenBirthModel::new(expander);
assert_eq!(birth.birth_components_vec().len(), 0);
assert!((birth.total_birth_mass() - 0.0).abs() < 1e-10);
let measurements = vec![
Measurement::from_array([10.0, 20.0]),
Measurement::from_array([30.0, 40.0]),
];
birth.set_measurements(measurements);
let components = birth.birth_components_vec();
assert_eq!(components.len(), 2);
assert!((birth.total_birth_mass() - 0.10).abs() < 1e-10);
assert!((components[0].mean.index(0) - 10.0).abs() < 1e-10);
assert!((components[0].mean.index(1) - 20.0).abs() < 1e-10);
birth.clear_measurements();
assert_eq!(birth.birth_components_vec().len(), 0);
}
#[cfg(feature = "alloc")]
#[test]
fn test_measurement_driven_birth_with_base_components() {
let expander = ConstantVelocity2DExpander::new(10.0, 100.0, 0.05);
let mut birth = MeasurementDrivenBirthModel::new(expander);
let base_mean = StateVector::from_array([50.0, 50.0, 0.0, 0.0]);
let base_cov = StateCovariance::identity();
birth.add_base_component(GaussianState::new(0.02, base_mean, base_cov));
birth.set_measurements(vec![Measurement::from_array([10.0, 20.0])]);
let components = birth.birth_components_vec();
assert_eq!(components.len(), 2);
assert!((birth.total_birth_mass() - 0.07).abs() < 1e-10);
}
#[cfg(feature = "alloc")]
#[test]
fn test_measurement_driven_birth_labeled() {
use crate::filters::lmb::LabeledBirthModel;
use crate::types::labels::LabelGenerator;
let expander = ConstantVelocity2DExpander::new(10.0, 100.0, 0.05);
let mut birth = MeasurementDrivenBirthModel::new(expander);
birth.set_measurements(vec![
Measurement::from_array([10.0, 20.0]),
Measurement::from_array([30.0, 40.0]),
]);
let mut label_gen = LabelGenerator::new();
let tracks = birth.birth_tracks(&mut label_gen);
assert_eq!(tracks.len(), 2);
assert_eq!(tracks[0].label.birth_time, 0);
assert_eq!(tracks[0].label.index, 0);
assert_eq!(tracks[1].label.birth_time, 0);
assert_eq!(tracks[1].label.index, 1);
assert!((tracks[0].existence - 0.05).abs() < 1e-10);
}
}