linkrs_query/optimizer/stats/feedback/
factor.rs1#[derive(Debug, Clone)]
10pub struct FeedbackDrivenFactor {
11 estimated: f64,
13 correction_factor: f64,
15 feedback_count: u64,
17 alpha: f64,
19 min_correction: f64,
21 max_correction: f64,
23 cumulative_estimation_error: f64,
25 error_sum_squares: f64,
27}
28
29impl FeedbackDrivenFactor {
30 pub fn new(estimated: f64) -> Self {
32 Self {
33 estimated,
34 correction_factor: 1.0,
35 feedback_count: 0,
36 alpha: 0.3,
37 min_correction: 0.1,
38 max_correction: 10.0,
39 cumulative_estimation_error: 0.0,
40 error_sum_squares: 0.0,
41 }
42 }
43
44 pub fn with_params(
46 estimated: f64,
47 alpha: f64,
48 min_correction: f64,
49 max_correction: f64,
50 ) -> Self {
51 Self {
52 estimated,
53 correction_factor: 1.0,
54 feedback_count: 0,
55 alpha,
56 min_correction,
57 max_correction,
58 cumulative_estimation_error: 0.0,
59 error_sum_squares: 0.0,
60 }
61 }
62
63 pub fn estimated(&self) -> f64 {
65 self.estimated
66 }
67
68 pub fn set_estimated(&mut self, estimated: f64) {
71 if estimated > 0.0 && estimated.is_finite() {
72 self.estimated = estimated;
73 }
74 }
75
76 pub fn corrected(&self) -> f64 {
78 self.estimated * self.correction_factor
79 }
80
81 pub fn correction_factor(&self) -> f64 {
83 self.correction_factor
84 }
85
86 pub fn feedback_count(&self) -> u64 {
88 self.feedback_count
89 }
90
91 pub fn estimation_confidence(&self) -> f64 {
95 let x = self.feedback_count as f64 * 0.1;
96 1.0 / (1.0 + (-x).exp())
97 }
98
99 pub fn avg_estimation_error(&self) -> f64 {
101 if self.feedback_count == 0 {
102 return 1.0;
103 }
104 self.cumulative_estimation_error / self.feedback_count as f64
105 }
106
107 pub fn error_std_dev(&self) -> f64 {
109 if self.feedback_count < 2 {
110 return 0.0;
111 }
112 let n = self.feedback_count as f64;
113 let mean = self.cumulative_estimation_error / n;
114 let variance = (self.error_sum_squares / n) - (mean * mean);
115 variance.max(0.0).sqrt()
116 }
117
118 pub fn update_with_ratio(&mut self, ratio: f64) {
121 if self.estimated <= 0.0 || !ratio.is_finite() {
122 return;
123 }
124 let error = (ratio - self.correction_factor).abs();
125 self.cumulative_estimation_error += error;
126 self.error_sum_squares += error * error;
127 self.correction_factor = (1.0 - self.alpha) * self.correction_factor + self.alpha * ratio;
128 self.correction_factor = self
129 .correction_factor
130 .clamp(self.min_correction, self.max_correction);
131 self.feedback_count += 1;
132 }
133
134 pub fn reset(&mut self) {
136 self.correction_factor = 1.0;
137 self.feedback_count = 0;
138 self.cumulative_estimation_error = 0.0;
139 self.error_sum_squares = 0.0;
140 }
141
142 pub fn set_alpha(&mut self, alpha: f64) {
144 self.alpha = alpha.clamp(0.0, 1.0);
145 }
146
147 pub fn set_correction_range(&mut self, min: f64, max: f64) {
149 self.min_correction = min.max(0.01);
150 self.max_correction = max.max(self.min_correction);
151 }
152}
153
154impl Default for FeedbackDrivenFactor {
155 fn default() -> Self {
156 Self::new(1.0)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_correction_converges_to_actual() {
166 let mut factor = FeedbackDrivenFactor::new(100.0);
167 for _ in 0..200 {
168 factor.update_with_ratio(2.0); }
170 assert!(factor.correction_factor() > 1.8);
171 assert!((factor.corrected() - 200.0).abs() < 25.0);
172 }
173
174 #[test]
175 fn test_correction_is_clamped() {
176 let mut factor = FeedbackDrivenFactor::with_params(100.0, 0.3, 0.1, 10.0);
177 for _ in 0..100 {
178 factor.update_with_ratio(100.0);
179 }
180 assert!(factor.correction_factor() <= 10.0);
181 }
182
183 #[test]
184 fn test_confidence_grows_with_feedback() {
185 let mut factor = FeedbackDrivenFactor::new(1.0);
186 assert!(factor.estimation_confidence() < 0.55);
187 for i in 0..100 {
188 factor.update_with_ratio(1.0 + i as f64 * 0.001);
189 }
190 assert!(factor.estimation_confidence() > 0.9);
191 }
192
193 #[test]
194 fn test_ignores_invalid_ratios() {
195 let mut factor = FeedbackDrivenFactor::new(10.0);
196 factor.update_with_ratio(f64::NAN);
197 factor.update_with_ratio(f64::INFINITY);
198 assert_eq!(factor.feedback_count(), 0);
199 }
200}