use std::any::Any;
use nalgebra::DVector;
use rand::prelude::*;
pub trait Particle: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn new(initial_state: &DVector<f64>, weight: f64) -> Self
where
Self: Sized;
fn state(&self) -> DVector<f64>;
fn set_state(&mut self, _state: DVector<f64>) {
panic!(
"Particle::set_state is not implemented for this type. \
Please provide an implementation in your Particle impl."
);
}
fn weight(&self) -> f64;
fn set_weight(&mut self, weight: f64);
}
#[derive(Clone, Copy, Debug, Default)]
pub enum ParticleAveragingStrategy {
Mean,
#[default]
WeightedMean,
HighestWeight,
}
#[derive(Clone, Copy, Debug, Default)]
pub enum ParticleResamplingStrategy {
Multinomial,
#[default]
Systematic,
Stratified,
Residual,
}
pub fn multinomial_resample<R: Rng>(
weights: &[f64],
num_samples: usize,
rng: &mut R,
) -> Vec<usize> {
let mut indices = Vec::with_capacity(num_samples);
let cumsum: Vec<f64> = weights
.iter()
.scan(0.0, |acc, &w| {
*acc += w;
Some(*acc)
})
.collect();
for _ in 0..num_samples {
let u: f64 = rng.random();
let idx = cumsum
.iter()
.position(|&c| c >= u)
.unwrap_or(weights.len() - 1);
indices.push(idx);
}
indices
}
pub fn systematic_resample<R: Rng>(weights: &[f64], num_samples: usize, rng: &mut R) -> Vec<usize> {
let mut indices = Vec::with_capacity(num_samples);
let step = 1.0 / num_samples as f64;
let start: f64 = rng.random::<f64>() * step;
let mut cumsum = 0.0;
let mut i = 0;
for j in 0..num_samples {
let u = start + (j as f64) * step;
while cumsum < u && i < weights.len() {
cumsum += weights[i];
i += 1;
}
let idx = if i > 0 { i - 1 } else { 0 };
indices.push(idx.min(weights.len() - 1));
}
indices
}
pub fn stratified_resample<R: Rng>(weights: &[f64], num_samples: usize, rng: &mut R) -> Vec<usize> {
let mut indices = Vec::with_capacity(num_samples);
let step = 1.0 / num_samples as f64;
let mut cumsum = 0.0;
let mut i = 0;
for j in 0..num_samples {
let u = (j as f64 + rng.random::<f64>()) * step;
while cumsum < u && i < weights.len() {
cumsum += weights[i];
i += 1;
}
let idx = if i > 0 { i - 1 } else { 0 };
indices.push(idx.min(weights.len() - 1));
}
indices
}
pub fn residual_resample<R: Rng>(weights: &[f64], num_samples: usize, rng: &mut R) -> Vec<usize> {
let mut indices = Vec::with_capacity(num_samples);
let n_weights: Vec<f64> = weights.iter().map(|&w| w * num_samples as f64).collect();
let mut residual_weights = Vec::with_capacity(weights.len());
for (i, &nw) in n_weights.iter().enumerate() {
let n_copies = nw.floor() as usize;
for _ in 0..n_copies {
indices.push(i);
}
residual_weights.push(nw - nw.floor());
}
let residual_sum: f64 = residual_weights.iter().sum();
if residual_sum > 0.0 {
for w in &mut residual_weights {
*w /= residual_sum;
}
}
let remaining = num_samples - indices.len();
if remaining > 0 {
let step = 1.0 / remaining as f64;
let start: f64 = rng.random::<f64>() * step;
let mut cumsum = 0.0;
let mut i = 0;
for j in 0..remaining {
let u = start + (j as f64) * step;
while cumsum < u && i < residual_weights.len() {
cumsum += residual_weights[i];
i += 1;
}
let idx = if i > 0 { i - 1 } else { 0 };
indices.push(idx.min(weights.len() - 1));
}
}
indices
}
pub trait ParticleFilter {
fn resample(&mut self);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standalone_multinomial_resample() {
let weights = vec![0.1, 0.3, 0.4, 0.2];
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let indices = multinomial_resample(&weights, 100, &mut rng);
assert_eq!(indices.len(), 100);
for &idx in &indices {
assert!(idx < weights.len());
}
let mut counts = vec![0; weights.len()];
for &idx in &indices {
counts[idx] += 1;
}
assert!(counts[2] > counts[0]); assert!(counts[1] > counts[0]); }
#[test]
fn test_standalone_systematic_resample() {
let weights = vec![0.25, 0.25, 0.25, 0.25]; let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let indices = systematic_resample(&weights, 100, &mut rng);
assert_eq!(indices.len(), 100);
let mut counts = vec![0; weights.len()];
for &idx in &indices {
counts[idx] += 1;
}
for count in counts {
let diff = if count > 25 { count - 25 } else { 25 - count };
assert!(diff <= 5); }
}
#[test]
fn test_standalone_stratified_resample() {
let weights = vec![0.1, 0.2, 0.3, 0.4];
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let indices = stratified_resample(&weights, 50, &mut rng);
assert_eq!(indices.len(), 50);
for &idx in &indices {
assert!(idx < weights.len());
}
}
#[test]
fn test_standalone_residual_resample() {
let weights = vec![0.1, 0.3, 0.4, 0.2];
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let indices = residual_resample(&weights, 10, &mut rng);
assert_eq!(indices.len(), 10);
let mut counts = vec![0; weights.len()];
for &idx in &indices {
counts[idx] += 1;
}
assert!(counts[2] >= 4); assert!(counts[1] >= 3); }
#[test]
fn test_resampling_functions_preserve_particle_count() {
let weights = vec![0.1, 0.2, 0.3, 0.4];
let num_samples = 100;
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let multinomial_indices = multinomial_resample(&weights, num_samples, &mut rng);
assert_eq!(multinomial_indices.len(), num_samples);
let systematic_indices = systematic_resample(&weights, num_samples, &mut rng);
assert_eq!(systematic_indices.len(), num_samples);
let stratified_indices = stratified_resample(&weights, num_samples, &mut rng);
assert_eq!(stratified_indices.len(), num_samples);
let residual_indices = residual_resample(&weights, num_samples, &mut rng);
assert_eq!(residual_indices.len(), num_samples);
}
}