scirs2_core/logging/progress/
adaptive.rs

1//! Adaptive progress tracking
2//!
3//! This module provides adaptive algorithms for intelligent progress tracking,
4//! including dynamic update rates and predictive ETA calculations.
5
6use std::collections::VecDeque;
7use std::time::{Duration, Instant};
8
9/// Adaptive update rate controller
10pub struct AdaptiveController {
11    /// Recent update intervals
12    update_intervals: VecDeque<Duration>,
13    /// Recent processing speeds
14    processing_speeds: VecDeque<f64>,
15    /// Minimum allowed update interval
16    min_interval: Duration,
17    /// Maximum allowed update interval
18    max_interval: Duration,
19    /// Target update frequency (updates per second)
20    target_frequency: f64,
21}
22
23impl AdaptiveController {
24    /// Create a new adaptive controller
25    pub fn new(min_interval: Duration, maxinterval: Duration) -> Self {
26        Self {
27            update_intervals: VecDeque::with_capacity(20),
28            processing_speeds: VecDeque::with_capacity(20),
29            min_interval,
30            max_interval: maxinterval,
31            target_frequency: 2.0, // 2 updates per second by default
32        }
33    }
34
35    /// Set the target update frequency
36    pub fn set_target_frequency(&mut self, frequency: f64) {
37        self.target_frequency = frequency.clamp(0.1, 10.0); // Clamp between 0.1 and 10 Hz
38    }
39
40    /// Record an update interval
41    pub fn record_update(&mut self, interval: Duration, processingspeed: f64) {
42        self.update_intervals.push_back(interval);
43        self.processing_speeds.push_back(processingspeed);
44
45        // Keep only recent measurements
46        if self.update_intervals.len() > 20 {
47            self.update_intervals.pop_front();
48            self.processing_speeds.pop_front();
49        }
50    }
51
52    /// Calculate the next optimal update interval
53    pub fn calculate_interval(&self, currentprogress: f64) -> Duration {
54        // Base interval from target frequency
55        let base_interval = Duration::from_secs_f64(1.0 / self.target_frequency);
56
57        // Adjust based on progress position
58        let position_factor = self.calculate_position_factor(currentprogress);
59
60        // Adjust based on processing speed stability
61        let stability_factor = self.calculate_stability_factor();
62
63        // Combine factors
64        let adjusted_interval = base_interval.mul_f64(position_factor * stability_factor);
65
66        // Clamp to min/max bounds
67        adjusted_interval
68            .max(self.min_interval)
69            .min(self.max_interval)
70    }
71
72    /// Calculate position-based adjustment factor
73    fn calculate_position_factor(&self, progress: f64) -> f64 {
74        // Update more frequently at start and end, less in the middle
75        // This creates a U-shaped curve
76        let normalized_progress = progress.clamp(0.0, 1.0);
77        let middle_distance = (0.5 - normalized_progress).abs() * 2.0; // 0 at middle, 1 at edges
78
79        // Factor ranges from 0.5 (at middle) to 1.0 (at edges)
80        0.5 + 0.5 * middle_distance
81    }
82
83    /// Calculate stability-based adjustment factor
84    fn calculate_stability_factor(&self) -> f64 {
85        if self.processing_speeds.len() < 2 {
86            return 1.0;
87        }
88
89        // Calculate coefficient of variation for processing speeds
90        let mean: f64 =
91            self.processing_speeds.iter().sum::<f64>() / self.processing_speeds.len() as f64;
92
93        if mean <= 0.0 {
94            return 1.0;
95        }
96
97        let variance: f64 = self
98            .processing_speeds
99            .iter()
100            .map(|&speed| (speed - mean).powi(2))
101            .sum::<f64>()
102            / self.processing_speeds.len() as f64;
103
104        let std_dev = variance.sqrt();
105        let cv = std_dev / mean; // Coefficient of variation
106
107        // If processing is stable (low CV), we can update less frequently
108        // If processing is unstable (high CV), we should update more frequently
109        // Factor ranges from 0.5 (very stable) to 2.0 (very unstable)
110        (1.0 + cv).clamp(0.5, 2.0)
111    }
112
113    /// Get average processing speed
114    pub fn average_speed(&self) -> f64 {
115        if self.processing_speeds.is_empty() {
116            return 0.0;
117        }
118
119        self.processing_speeds.iter().sum::<f64>() / self.processing_speeds.len() as f64
120    }
121
122    /// Predict if processing speed is increasing or decreasing
123    pub fn speed_trend(&self) -> SpeedTrend {
124        if self.processing_speeds.len() < 3 {
125            return SpeedTrend::Stable;
126        }
127
128        let recent_half = self.processing_speeds.len() / 2;
129        let early_speeds: f64 =
130            self.processing_speeds.iter().take(recent_half).sum::<f64>() / recent_half as f64;
131
132        let late_speeds: f64 = self.processing_speeds.iter().skip(recent_half).sum::<f64>()
133            / (self.processing_speeds.len() - recent_half) as f64;
134
135        let change_ratio = if early_speeds > 0.0 {
136            late_speeds / early_speeds
137        } else {
138            1.0
139        };
140
141        if change_ratio > 1.1 {
142            SpeedTrend::Increasing
143        } else if change_ratio < 0.9 {
144            SpeedTrend::Decreasing
145        } else {
146            SpeedTrend::Stable
147        }
148    }
149}
150
151/// Processing speed trend
152#[derive(Debug, Clone, Copy, PartialEq)]
153pub enum SpeedTrend {
154    /// Processing speed is increasing
155    Increasing,
156    /// Processing speed is decreasing
157    Decreasing,
158    /// Processing speed is stable
159    Stable,
160}
161
162/// Predictive ETA calculator
163pub struct PredictiveETA {
164    /// Historical progress measurements
165    progress_history: VecDeque<(Instant, u64)>,
166    /// Historical speed measurements
167    speed_history: VecDeque<f64>,
168    /// Maximum history length
169    max_history: usize,
170}
171
172impl PredictiveETA {
173    /// Create a new predictive ETA calculator
174    pub fn new() -> Self {
175        Self {
176            progress_history: VecDeque::with_capacity(50),
177            speed_history: VecDeque::with_capacity(50),
178            max_history: 50,
179        }
180    }
181
182    /// Record a progress measurement
183    pub fn record_progress(&mut self, time: Instant, processed: u64, speed: f64) {
184        self.progress_history.push_back((time, processed));
185        self.speed_history.push_back(speed);
186
187        // Maintain maximum history size
188        if self.progress_history.len() > self.max_history {
189            self.progress_history.pop_front();
190            self.speed_history.pop_front();
191        }
192    }
193
194    /// Calculate predictive ETA using multiple methods
195    pub fn calculate_eta(&self, currentprocessed: u64, total: u64) -> Duration {
196        if currentprocessed >= total || self.progress_history.len() < 2 {
197            return Duration::from_secs(0);
198        }
199
200        let remaining = total - currentprocessed;
201
202        // Method 1: Linear regression on recent progress
203        let linear_eta = self.linear_regression_eta(remaining);
204
205        // Method 2: Exponential smoothing of speeds
206        let smoothed_eta = self.exponential_smoothing_eta(remaining);
207
208        // Method 3: Simple moving average
209        let average_eta = self.moving_average_eta(remaining);
210
211        // Combine methods with weights based on data quality
212        let weights = self.calculate_method_weights();
213
214        let combined_seconds = linear_eta.as_secs_f64() * weights.0
215            + smoothed_eta.as_secs_f64() * weights.1
216            + average_eta.as_secs_f64() * weights.2;
217
218        Duration::from_secs_f64(combined_seconds.max(0.0))
219    }
220
221    /// Calculate ETA using linear regression
222    fn linear_regression_eta(&self, remaining: u64) -> Duration {
223        if self.progress_history.len() < 3 {
224            return self.moving_average_eta(remaining);
225        }
226
227        // Perform simple linear regression on time vs progress
228        let n = self.progress_history.len() as f64;
229        let start_time = self.progress_history[0].0;
230
231        let (sum_x, sum_y, sum_xy, sum_x2) = self.progress_history.iter().fold(
232            (0.0, 0.0, 0.0, 0.0),
233            |(sx, sy, sxy, sx2), &(time, progress)| {
234                let x = time.duration_since(start_time).as_secs_f64();
235                let y = progress as f64;
236                (sx + x, sy + y, sxy + x * y, sx2 + x * x)
237            },
238        );
239
240        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
241
242        if slope > 0.0 {
243            let remaining_time = remaining as f64 / slope;
244            Duration::from_secs_f64(remaining_time)
245        } else {
246            self.moving_average_eta(remaining)
247        }
248    }
249
250    /// Calculate ETA using exponential smoothing
251    fn exponential_smoothing_eta(&self, remaining: u64) -> Duration {
252        if self.speed_history.is_empty() {
253            return Duration::from_secs(0);
254        }
255
256        // Apply exponential smoothing to recent speeds
257        let alpha = 0.3; // Smoothing factor
258        let mut smoothed_speed = self.speed_history[0];
259
260        for &speed in self.speed_history.iter().skip(1) {
261            smoothed_speed = alpha * speed + (1.0 - alpha) * smoothed_speed;
262        }
263
264        if smoothed_speed > 0.0 {
265            Duration::from_secs_f64(remaining as f64 / smoothed_speed)
266        } else {
267            Duration::from_secs(0)
268        }
269    }
270
271    /// Calculate ETA using moving average
272    fn moving_average_eta(&self, remaining: u64) -> Duration {
273        if self.speed_history.is_empty() {
274            return Duration::from_secs(0);
275        }
276
277        let recent_count = (self.speed_history.len() / 2).clamp(1, 10);
278        let recent_speeds: Vec<_> = self.speed_history.iter().rev().take(recent_count).collect();
279
280        let avg_speed: f64 =
281            recent_speeds.iter().map(|&&s| s).sum::<f64>() / recent_speeds.len() as f64;
282
283        if avg_speed > 0.0 {
284            Duration::from_secs_f64(remaining as f64 / avg_speed)
285        } else {
286            Duration::from_secs(0)
287        }
288    }
289
290    /// Calculate weights for combining different ETA methods
291    fn calculate_method_weights(&self) -> (f64, f64, f64) {
292        let history_len = self.progress_history.len();
293
294        if history_len < 3 {
295            // Not enough data for linear regression, rely on others
296            (0.0, 0.4, 0.6)
297        } else if history_len < 10 {
298            // Some data, but still prefer simpler methods
299            (0.3, 0.4, 0.3)
300        } else {
301            // Enough data for reliable linear regression
302            (0.5, 0.3, 0.2)
303        }
304    }
305}
306
307impl Default for PredictiveETA {
308    fn default() -> Self {
309        Self::new()
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316
317    #[test]
318    fn test_adaptive_controller_creation() {
319        let controller =
320            AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
321
322        assert_eq!(controller.min_interval, Duration::from_millis(100));
323        assert_eq!(controller.max_interval, Duration::from_secs(1));
324    }
325
326    #[test]
327    fn test_adaptive_controller_position_factor() {
328        let controller =
329            AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
330
331        // Test U-shaped curve - more frequent updates at start and end
332        let start_factor = controller.calculate_position_factor(0.0);
333        let middle_factor = controller.calculate_position_factor(0.5);
334        let end_factor = controller.calculate_position_factor(1.0);
335
336        assert!(start_factor > middle_factor);
337        assert!(end_factor > middle_factor);
338        assert!((start_factor - end_factor).abs() < 0.1);
339    }
340
341    #[test]
342    fn test_speed_trend_detection() {
343        let mut controller =
344            AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
345
346        // Simulate increasing speeds
347        for i in 1..=10 {
348            controller.record_update(Duration::from_millis(100), i as f64 * 10.0);
349        }
350
351        assert_eq!(controller.speed_trend(), SpeedTrend::Increasing);
352    }
353
354    #[test]
355    fn test_predictive_eta() {
356        let mut eta_calc = PredictiveETA::new();
357        let start_time = Instant::now();
358
359        // Simulate steady progress
360        for i in 1..=10 {
361            let time = start_time + Duration::from_secs(i);
362            eta_calc.record_progress(time, i * 10, 10.0);
363        }
364
365        let eta = eta_calc.calculate_eta(50, 100);
366
367        // Should estimate roughly 5 seconds remaining (50 items at 10 items/sec)
368        assert!((eta.as_secs_f64() - 5.0).abs() < 2.0);
369    }
370}