lib.rs
raw
#[doc = include_str!("../README.md")]
use std::fmt::Debug;
use thiserror::Error;
pub mod ekf;
pub mod particle;
/// Errors that can occur during state estimation.
#[derive(Debug, Error)]
pub enum EstimatorError {
/// The filter's internal math produced a matrix that can't be inverted.
///
/// This usually means your measurement noise values are too small.
/// Try increasing `measurement_noise_std` in your measurement model.
#[error(
"Could not compute the measurement update. \
This usually means measurement noise is set too low. \
Try increasing measurement_noise_std values."
)]
SingularInnovationCovariance,
/// All particle weights dropped to zero.
///
/// This happens when no particles match the measurements at all.
/// Possible causes:
/// - Initial particles are too far from the true state
/// - Process noise is too low (particles aren't spreading enough)
/// - Measurement model is too strict
#[error(
"All particle weights are zero — no particles match the measurements. \
Try: (1) more particles, (2) wider initial distribution, \
(3) more process noise, or (4) less strict likelihood function."
)]
ParticleDegeneracy,
/// The filter has no particles.
#[error("Particle filter has no particles")]
NoParticles,
/// A numerical computation produced NaN or infinity.
#[error("Numerical instability: computation produced {0}")]
NumericalInstability(String),
}
/// Result type for estimator operations.
pub type Result<T> = std::result::Result<T, EstimatorError>;
/// A state estimator that tracks something over time.
///
/// All estimators follow the same pattern:
/// 1. **Predict**: Use your model to guess where things will be
/// 2. **Update**: Use a measurement to correct your guess
/// 3. **Read**: Get the current best estimate
pub trait StateEstimator {
/// What you're tracking (position, velocity, temperature, etc.)
type State: Clone + Debug;
/// What your sensors report
type Measurement;
/// Control inputs to your system (use `()` if there are none)
type Control;
/// Predict where the state will be after `dt` seconds.
///
/// Call this once per time step, before you get a new measurement.
/// This step rarely fails — it just propagates your model forward.
fn predict(&mut self, control: &Self::Control, dt: f64);
/// Update the estimate using a new sensor measurement.
///
/// This corrects the prediction based on what you actually observed.
///
/// # Errors
///
/// Can fail if:
/// - Measurement noise is set too low (EKF)
/// - All particles have zero weight (Particle Filter)
fn update(&mut self, measurement: &Self::Measurement) -> Result<()>;
/// Get the current best estimate of the state.
fn estimate(&self) -> Self::State;
/// Convenience method: predict then update in one call.
///
/// # Errors
///
/// Returns an error if the update step fails.
fn step(
&mut self,
control: &Self::Control,
measurement: &Self::Measurement,
dt: f64,
) -> Result<Self::State> {
self.predict(control, dt);
self.update(measurement)?;
Ok(self.estimate())
}
}
/// For estimators that can tell you how confident they are.
pub trait UncertaintyEstimator: StateEstimator {
/// Get the standard deviation (uncertainty) for each state variable.
///
/// Smaller values = more confident in the estimate.
fn uncertainty(&self) -> Vec<f64>;
}
/// For particle-based estimators.
pub trait ParticleEstimator: StateEstimator {
/// Iterate over all particles and their weights.
fn particles(&self) -> impl Iterator<Item = (&Self::State, f64)>;
/// Number of particles being used.
fn num_particles(&self) -> usize;
/// How "healthy" the particle distribution is (1.0 = perfect, 0.0 = degenerate).
///
/// If this drops too low, the filter automatically resamples.
fn particle_diversity(&self) -> f64;
}
#[cfg(test)]
mod tests;