Skip to main content

aegis_filter/
robust.rs

1//! Robust & Adaptive State Estimation.
2//!
3//! Features:
4//! - **H-infinity (H_inf) Bounded-Error Filter**: Minimizes worst-case estimation error
5//!   regardless of noise probability distribution, making it impervious to non-Gaussian
6//!   sensor anomalies and adversarial attacks.
7//! - **Adaptive Innovation Noise Estimator**: Automatically tunes measurement noise (R)
8//!   at runtime using residual variance matching.
9
10use crate::common::FilterError;
11
12/// 1D Robust H-infinity (H_inf) Filter.
13///
14/// Unlike the Kalman Filter, which minimizes the *mean-square error* under the assumption
15/// of known Gaussian noise, the H-infinity filter solves a minimax game:
16/// it minimizes the *worst-case estimation error* over all possible bounded disturbances.
17#[derive(Debug, Clone, Copy)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct HInfinityFilter1D {
20    /// State estimate (x)
21    x: f64,
22    /// Covariance / error bound parameter (P)
23    p: f64,
24    /// Process disturbance bound (Q)
25    q: f64,
26    /// Measurement disturbance bound (R)
27    r: f64,
28    /// Performance attenuation bound (gamma). Smaller values enforce tighter worst-case error bounds.
29    gamma: f64,
30}
31
32impl HInfinityFilter1D {
33    /// Creates a new H-infinity filter.
34    /// `gamma` must be strictly greater than 0 (typically >= 1.0).
35    pub fn new(
36        initial_state: f64,
37        initial_variance: f64,
38        process_bound_q: f64,
39        measurement_bound_r: f64,
40        gamma: f64,
41    ) -> Result<Self, FilterError> {
42        if !initial_state.is_finite()
43            || initial_variance <= 0.0
44            || process_bound_q < 0.0
45            || measurement_bound_r <= 0.0
46            || gamma <= 0.0
47        {
48            return Err(FilterError::InvalidCovariance);
49        }
50
51        Ok(Self {
52            x: initial_state,
53            p: initial_variance,
54            q: process_bound_q,
55            r: measurement_bound_r,
56            gamma,
57        })
58    }
59
60    /// Time update:
61    /// x = x + u
62    /// P = P + Q
63    pub fn predict(&mut self, control_input: f64) -> Result<(), FilterError> {
64        if !control_input.is_finite() {
65            return Err(FilterError::NonFiniteInput);
66        }
67        self.x += control_input;
68        self.p += self.q;
69        Ok(())
70    }
71
72    /// Measurement update under the H-infinity minimax criteria:
73    ///
74    /// Stability condition requires: `1 - gamma^(-2) * P + (H^2 * P) / R > 0`
75    pub fn update(&mut self, measurement: f64) -> Result<f64, FilterError> {
76        if !measurement.is_finite() {
77            return Err(FilterError::NonFiniteInput);
78        }
79
80        let gamma_sq = self.gamma * self.gamma;
81        let _h_term = 1.0; // H = 1.0 for direct scalar measurement
82
83        // H-infinity denominator term:
84        // D = 1 - (1.0 / gamma_sq) * P + (H^2 * P) / R
85        let d = 1.0 - (self.p / gamma_sq) + (self.p / self.r);
86
87        // If the H-infinity bound is violated, the system cannot guarantee bounded error
88        if d <= 1e-12 || !d.is_finite() {
89            return Err(FilterError::NumericalInstability);
90        }
91
92        // H-infinity Gain:
93        // K = P / (R * D)
94        let k = self.p / (self.r * d);
95
96        // State update
97        let residual = measurement - self.x;
98        self.x += k * residual;
99
100        // Covariance update: P = P / D
101        self.p /= d;
102
103        if self.p < 1e-15 {
104            self.p = 1e-15;
105        }
106
107        Ok(self.x)
108    }
109
110    #[inline(always)]
111    pub fn state(&self) -> f64 {
112        self.x
113    }
114
115    #[inline(always)]
116    pub fn bound(&self) -> f64 {
117        self.p
118    }
119}
120
121/// Adaptive Innovation Noise Estimator.
122/// Automatically adjusts measurement variance R online using an exponential moving average
123/// of the innovation sequence (Sage-Husa inspired).
124#[derive(Debug, Clone)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct AdaptiveFilter1D {
127    pub x: f64,
128    pub p: f64,
129    pub q: f64,
130    pub r: f64,
131    /// Smoothing factor for online noise estimation (0.01 to 0.1)
132    pub alpha: f64,
133}
134
135impl AdaptiveFilter1D {
136    pub fn new(
137        initial_x: f64,
138        initial_p: f64,
139        q: f64,
140        initial_r: f64,
141        adaptation_rate: f64,
142    ) -> Result<Self, FilterError> {
143        if !initial_x.is_finite()
144            || initial_p <= 0.0
145            || q < 0.0
146            || initial_r <= 0.0
147            || adaptation_rate <= 0.0
148            || adaptation_rate >= 1.0
149        {
150            return Err(FilterError::InvalidCovariance);
151        }
152
153        Ok(Self {
154            x: initial_x,
155            p: initial_p,
156            q,
157            r: initial_r,
158            alpha: adaptation_rate,
159        })
160    }
161
162    pub fn predict(&mut self, u: f64) {
163        self.x += u;
164        self.p += self.q;
165    }
166
167    pub fn update(&mut self, z: f64) -> Result<f64, FilterError> {
168        if !z.is_finite() {
169            return Err(FilterError::NonFiniteInput);
170        }
171
172        let y = z - self.x;
173        let s = self.p + self.r;
174
175        let k = self.p / s;
176        self.x += k * y;
177
178        // Joseph form update
179        let one_minus_k = 1.0 - k;
180        self.p = (one_minus_k * one_minus_k * self.p) + (k * k * self.r);
181
182        // Adaptive estimation of R:
183        // Instantaneous residual variance estimation: R_est = y^2 - P
184        let r_sample = y * y - self.p;
185        if r_sample > 1e-6 {
186            self.r = (1.0 - self.alpha) * self.r + self.alpha * r_sample;
187        }
188
189        Ok(self.x)
190    }
191
192    #[inline(always)]
193    pub fn estimated_measurement_noise(&self) -> f64 {
194        self.r
195    }
196}