#[derive(Debug, Clone)]
pub struct FeedbackDrivenFactor {
estimated: f64,
correction_factor: f64,
feedback_count: u64,
alpha: f64,
min_correction: f64,
max_correction: f64,
cumulative_estimation_error: f64,
error_sum_squares: f64,
}
impl FeedbackDrivenFactor {
pub fn new(estimated: f64) -> Self {
Self {
estimated,
correction_factor: 1.0,
feedback_count: 0,
alpha: 0.3,
min_correction: 0.1,
max_correction: 10.0,
cumulative_estimation_error: 0.0,
error_sum_squares: 0.0,
}
}
pub fn with_params(
estimated: f64,
alpha: f64,
min_correction: f64,
max_correction: f64,
) -> Self {
Self {
estimated,
correction_factor: 1.0,
feedback_count: 0,
alpha,
min_correction,
max_correction,
cumulative_estimation_error: 0.0,
error_sum_squares: 0.0,
}
}
pub fn estimated(&self) -> f64 {
self.estimated
}
pub fn set_estimated(&mut self, estimated: f64) {
if estimated > 0.0 && estimated.is_finite() {
self.estimated = estimated;
}
}
pub fn corrected(&self) -> f64 {
self.estimated * self.correction_factor
}
pub fn correction_factor(&self) -> f64 {
self.correction_factor
}
pub fn feedback_count(&self) -> u64 {
self.feedback_count
}
pub fn estimation_confidence(&self) -> f64 {
let x = self.feedback_count as f64 * 0.1;
1.0 / (1.0 + (-x).exp())
}
pub fn avg_estimation_error(&self) -> f64 {
if self.feedback_count == 0 {
return 1.0;
}
self.cumulative_estimation_error / self.feedback_count as f64
}
pub fn error_std_dev(&self) -> f64 {
if self.feedback_count < 2 {
return 0.0;
}
let n = self.feedback_count as f64;
let mean = self.cumulative_estimation_error / n;
let variance = (self.error_sum_squares / n) - (mean * mean);
variance.max(0.0).sqrt()
}
pub fn update_with_ratio(&mut self, ratio: f64) {
if self.estimated <= 0.0 || !ratio.is_finite() {
return;
}
let error = (ratio - self.correction_factor).abs();
self.cumulative_estimation_error += error;
self.error_sum_squares += error * error;
self.correction_factor = (1.0 - self.alpha) * self.correction_factor + self.alpha * ratio;
self.correction_factor = self
.correction_factor
.clamp(self.min_correction, self.max_correction);
self.feedback_count += 1;
}
pub fn reset(&mut self) {
self.correction_factor = 1.0;
self.feedback_count = 0;
self.cumulative_estimation_error = 0.0;
self.error_sum_squares = 0.0;
}
pub fn set_alpha(&mut self, alpha: f64) {
self.alpha = alpha.clamp(0.0, 1.0);
}
pub fn set_correction_range(&mut self, min: f64, max: f64) {
self.min_correction = min.max(0.01);
self.max_correction = max.max(self.min_correction);
}
}
impl Default for FeedbackDrivenFactor {
fn default() -> Self {
Self::new(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_correction_converges_to_actual() {
let mut factor = FeedbackDrivenFactor::new(100.0);
for _ in 0..200 {
factor.update_with_ratio(2.0); }
assert!(factor.correction_factor() > 1.8);
assert!((factor.corrected() - 200.0).abs() < 25.0);
}
#[test]
fn test_correction_is_clamped() {
let mut factor = FeedbackDrivenFactor::with_params(100.0, 0.3, 0.1, 10.0);
for _ in 0..100 {
factor.update_with_ratio(100.0);
}
assert!(factor.correction_factor() <= 10.0);
}
#[test]
fn test_confidence_grows_with_feedback() {
let mut factor = FeedbackDrivenFactor::new(1.0);
assert!(factor.estimation_confidence() < 0.55);
for i in 0..100 {
factor.update_with_ratio(1.0 + i as f64 * 0.001);
}
assert!(factor.estimation_confidence() > 0.9);
}
#[test]
fn test_ignores_invalid_ratios() {
let mut factor = FeedbackDrivenFactor::new(10.0);
factor.update_with_ratio(f64::NAN);
factor.update_with_ratio(f64::INFINITY);
assert_eq!(factor.feedback_count(), 0);
}
}