sorting_race/services/fairness/
walltime.rs

1//! Wall-time based fairness model implementation
2
3use crate::models::traits::{FairnessModel, Sorter};
4use std::time::Duration;
5use std::collections::HashMap;
6
7/// Fairness model that allocates budgets based on wall-clock time slices
8/// Each algorithm gets equal time slices for fair execution
9#[derive(Debug)]
10pub struct WallTimeFairness {
11    slice_ms: u64,  // Time slice in milliseconds
12    algorithm_timings: HashMap<String, Duration>,  // Last measured execution times
13    algorithm_speeds: HashMap<String, f64>,        // Operations per millisecond estimate
14}
15
16impl WallTimeFairness {
17    /// Create a new wall-time fairness model
18    /// 
19    /// # Arguments
20    /// * `slice_ms` - Time slice in milliseconds that each algorithm should get
21    pub fn new(slice_ms: u64) -> Self {
22        Self {
23            slice_ms: slice_ms.max(1),
24            algorithm_timings: HashMap::new(),
25            algorithm_speeds: HashMap::new(),
26        }
27    }
28
29    /// Update timing information for an algorithm after execution
30    pub fn update_timing(&mut self, algorithm_name: &str, duration: Duration, operations_performed: usize) {
31        self.algorithm_timings.insert(algorithm_name.to_string(), duration);
32        
33        if duration.as_millis() > 0 {
34            let speed = operations_performed as f64 / duration.as_millis() as f64;
35            self.algorithm_speeds.insert(algorithm_name.to_string(), speed);
36        }
37    }
38
39    /// Estimate how many operations an algorithm can perform in the time slice
40    fn estimate_operations_for_time_slice(&self, algorithm_name: &str) -> usize {
41        let default_speed = 10.0; // Default: 10 operations per millisecond
42        let speed = self.algorithm_speeds.get(algorithm_name).copied().unwrap_or(default_speed);
43        ((speed * self.slice_ms as f64).round() as usize).max(1)
44    }
45
46    /// Get the time slice in milliseconds
47    pub fn get_slice_ms(&self) -> u64 {
48        self.slice_ms
49    }
50
51    /// Clear all timing history
52    pub fn clear_history(&mut self) {
53        self.algorithm_timings.clear();
54        self.algorithm_speeds.clear();
55    }
56}
57
58impl Default for WallTimeFairness {
59    fn default() -> Self {
60        Self::new(50) // 50ms time slice
61    }
62}
63
64impl FairnessModel for WallTimeFairness {
65    fn allocate_budget(&self, algorithms: &[Box<dyn Sorter>]) -> Vec<usize> {
66        algorithms
67            .iter()
68            .map(|algorithm| {
69                if algorithm.is_complete() {
70                    0 // No budget for completed algorithms
71                } else {
72                    // Estimate budget based on time slice and algorithm speed
73                    self.estimate_operations_for_time_slice(algorithm.name())
74                }
75            })
76            .collect()
77    }
78
79    fn name(&self) -> &str {
80        "Wall Time Fairness"
81    }
82}
83
84/// Adaptive wall-time fairness that learns from algorithm performance
85#[derive(Debug)]
86pub struct AdaptiveWallTimeFairness {
87    base_fairness: WallTimeFairness,
88    performance_multipliers: HashMap<String, f64>,
89    adaptation_rate: f64,
90}
91
92impl AdaptiveWallTimeFairness {
93    /// Create a new adaptive wall-time fairness model
94    pub fn new(slice_ms: u64, adaptation_rate: f64) -> Self {
95        Self {
96            base_fairness: WallTimeFairness::new(slice_ms),
97            performance_multipliers: HashMap::new(),
98            adaptation_rate: adaptation_rate.clamp(0.01, 1.0),
99        }
100    }
101
102    /// Update performance multiplier based on algorithm efficiency
103    pub fn update_performance(&mut self, algorithm_name: &str, efficiency: f64) {
104        let multiplier = self.performance_multipliers
105            .entry(algorithm_name.to_string())
106            .or_insert(1.0);
107
108        // Adaptive learning: slowly adjust multiplier based on efficiency
109        *multiplier = *multiplier * (1.0 - self.adaptation_rate) + efficiency * self.adaptation_rate;
110        
111        // Clamp multiplier to reasonable bounds
112        *multiplier = multiplier.clamp(0.1, 3.0);
113    }
114
115    /// Get performance multiplier for an algorithm
116    fn get_performance_multiplier(&self, algorithm_name: &str) -> f64 {
117        self.performance_multipliers.get(algorithm_name).copied().unwrap_or(1.0)
118    }
119
120    /// Clear performance history
121    pub fn clear_performance_history(&mut self) {
122        self.performance_multipliers.clear();
123    }
124
125    /// Update timing information for an algorithm
126    pub fn update_timing(&mut self, algorithm_name: &str, duration: Duration, operations_performed: usize) {
127        self.base_fairness.update_timing(algorithm_name, duration, operations_performed);
128    }
129}
130
131impl Default for AdaptiveWallTimeFairness {
132    fn default() -> Self {
133        Self::new(50, 0.1) // 50ms slice, 10% adaptation rate
134    }
135}
136
137impl FairnessModel for AdaptiveWallTimeFairness {
138    fn allocate_budget(&self, algorithms: &[Box<dyn Sorter>]) -> Vec<usize> {
139        let base_budgets = self.base_fairness.allocate_budget(algorithms);
140        
141        base_budgets
142            .into_iter()
143            .zip(algorithms.iter())
144            .map(|(budget, algorithm)| {
145                if budget == 0 {
146                    0 // Keep zero budget for completed algorithms
147                } else {
148                    let multiplier = self.get_performance_multiplier(algorithm.name());
149                    ((budget as f64) * multiplier).round() as usize
150                }
151            })
152            .collect()
153    }
154
155    fn name(&self) -> &str {
156        "Adaptive Wall Time"
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::services::sorters::bubble::BubbleSort;
164    
165    use std::time::Duration;
166
167    #[test]
168    fn test_walltime_fairness_timing() {
169        let mut fairness = WallTimeFairness::new(50);
170        
171        let duration = Duration::from_millis(10);
172        fairness.update_timing("Test Algorithm", duration, 100);
173        
174        let budget = fairness.estimate_operations_for_time_slice("Test Algorithm");
175        assert!(budget > 0);
176    }
177
178    #[test]
179    fn test_walltime_fairness_budget_calculation() {
180        let mut fairness = WallTimeFairness::new(100);
181        
182        // Update timing for different speed algorithms
183        fairness.update_timing("Fast", Duration::from_millis(10), 200); // 20 ops/ms
184        fairness.update_timing("Slow", Duration::from_millis(50), 100); // 2 ops/ms
185        
186        let mut bubble_fast = BubbleSort::new();
187        let mut bubble_slow = BubbleSort::new();
188        bubble_fast.reset(vec![1, 2, 3]);
189        bubble_slow.reset(vec![3, 2, 1]);
190        
191        let algorithms: Vec<Box<dyn Sorter>> = vec![
192            Box::new(bubble_fast),
193            Box::new(bubble_slow),
194        ];
195
196        let budgets = fairness.allocate_budget(&algorithms);
197        
198        // Both should get some budget
199        assert!(budgets.iter().all(|&b| b > 0));
200        
201        // Fast algorithm should get higher budget due to higher speed
202        // (Note: This test assumes algorithm names match but in practice 
203        // the fairness model uses default speeds for unknown algorithms)
204    }
205
206    #[test]
207    fn test_adaptive_walltime_fairness_performance_update() {
208        let mut fairness = AdaptiveWallTimeFairness::new(100, 0.2);
209        
210        // Update performance for an algorithm
211        fairness.update_performance("Test Algorithm", 2.0);
212        let result = fairness.get_performance_multiplier("Test Algorithm");
213        let expected = 1.2; // 1.0 * 0.8 + 2.0 * 0.2
214        assert!((result - expected).abs() < 0.000001, "Expected {}, got {}", expected, result);
215        
216        // Update again
217        fairness.update_performance("Test Algorithm", 0.5);
218        let expected = 1.2 * 0.8 + 0.5 * 0.2; // Should be around 1.06
219        let result = fairness.get_performance_multiplier("Test Algorithm");
220        assert!((result - expected).abs() < 0.000001, "Expected {}, got {}", expected, result);
221    }
222
223    #[test]
224    fn test_walltime_fairness_name() {
225        let fairness = WallTimeFairness::default();
226        assert_eq!(fairness.name(), "Wall Time Fairness");
227        
228        let adaptive_fairness = AdaptiveWallTimeFairness::default();
229        assert_eq!(adaptive_fairness.name(), "Adaptive Wall Time");
230    }
231}