sorting_race/services/fairness/
weighted.rs1use crate::models::traits::{FairnessModel, Sorter};
4use std::collections::HashMap;
5
6#[derive(Debug)]
9pub struct WeightedFairness {
10 alpha: f32, beta: f32, base_budget: usize,
13}
14
15impl WeightedFairness {
16 pub fn new(alpha: f32, beta: f32) -> Self {
22 Self {
23 alpha,
24 beta,
25 base_budget: 100, }
27 }
28
29 fn calculate_weighted_score(&self, algorithm: &dyn Sorter) -> f32 {
31 let telemetry = algorithm.get_telemetry();
32 self.alpha * telemetry.total_comparisons as f32 + self.beta * telemetry.total_moves as f32
33 }
34
35 pub fn set_base_budget(&mut self, budget: usize) {
37 self.base_budget = budget.max(1);
38 }
39
40 pub fn get_base_budget(&self) -> usize {
42 self.base_budget
43 }
44}
45
46impl Default for WeightedFairness {
47 fn default() -> Self {
48 Self::new(1.0, 1.0) }
50}
51
52impl FairnessModel for WeightedFairness {
53 fn allocate_budget(&self, algorithms: &[Box<dyn Sorter>]) -> Vec<usize> {
54 let active_algorithms: Vec<(usize, f32)> = algorithms
56 .iter()
57 .enumerate()
58 .filter_map(|(i, alg)| {
59 if alg.is_complete() {
60 None
61 } else {
62 Some((i, self.calculate_weighted_score(alg.as_ref())))
63 }
64 })
65 .collect();
66
67 if active_algorithms.is_empty() {
69 return vec![0; algorithms.len()];
70 }
71
72 if active_algorithms.len() == 1 {
73 let mut budgets = vec![0; algorithms.len()];
74 budgets[active_algorithms[0].0] = self.base_budget;
75 return budgets;
76 }
77
78 if self.alpha == 0.0 && self.beta == 0.0 {
80 let equal_budget = self.base_budget;
82 return algorithms
83 .iter()
84 .map(|alg| if alg.is_complete() { 0 } else { equal_budget })
85 .collect();
86 }
87
88 let total_score: f32 = active_algorithms.iter().map(|(_, score)| *score).sum();
90
91 let mut budgets = vec![0; algorithms.len()];
93 let total_budget = self.base_budget * active_algorithms.len();
94
95 if total_score == 0.0 {
96 let equal_budget = total_budget / active_algorithms.len();
98 for (idx, _) in active_algorithms {
99 budgets[idx] = equal_budget;
100 }
101 } else {
102 for (idx, score) in &active_algorithms {
104 let max_score = active_algorithms.iter().map(|(_, s)| *s).fold(0.0f32, f32::max);
106 let inverse_score = max_score - score + 1.0; let total_inverse: f32 = active_algorithms.iter()
108 .map(|(_, s)| max_score - s + 1.0)
109 .sum();
110
111 let budget = ((total_budget as f32 * inverse_score) / total_inverse).round() as usize;
112 budgets[*idx] = budget.max(1); }
114 }
115
116 budgets
117 }
118
119 fn name(&self) -> &str {
120 "Weighted Fairness"
121 }
122}
123
124#[derive(Debug)]
126pub struct PerformanceWeightedFairness {
127 base_budget: usize,
128 efficiency_history: HashMap<String, Vec<f32>>,
129 history_window: usize,
130}
131
132impl PerformanceWeightedFairness {
133 pub fn new(base_budget: usize, history_window: usize) -> Self {
135 Self {
136 base_budget: base_budget.max(1),
137 efficiency_history: HashMap::new(),
138 history_window: history_window.max(1),
139 }
140 }
141
142 pub fn record_efficiency(&mut self, algorithm_name: &str, efficiency: f32) {
144 let history = self.efficiency_history
145 .entry(algorithm_name.to_string())
146 .or_default();
147
148 history.push(efficiency.max(0.0));
149
150 if history.len() > self.history_window {
152 history.remove(0);
153 }
154 }
155
156 fn get_average_efficiency(&self, algorithm_name: &str) -> f32 {
158 if let Some(history) = self.efficiency_history.get(algorithm_name) {
159 if !history.is_empty() {
160 history.iter().sum::<f32>() / history.len() as f32
161 } else {
162 1.0 }
164 } else {
165 1.0 }
167 }
168
169 pub fn clear_history(&mut self) {
171 self.efficiency_history.clear();
172 }
173}
174
175impl Default for PerformanceWeightedFairness {
176 fn default() -> Self {
177 Self::new(10, 10)
178 }
179}
180
181impl FairnessModel for PerformanceWeightedFairness {
182 fn allocate_budget(&self, algorithms: &[Box<dyn Sorter>]) -> Vec<usize> {
183 let total_efficiency: f32 = algorithms
185 .iter()
186 .filter(|alg| !alg.is_complete())
187 .map(|alg| self.get_average_efficiency(alg.name()))
188 .sum();
189
190 if total_efficiency == 0.0 {
191 return algorithms
193 .iter()
194 .map(|alg| if alg.is_complete() { 0 } else { self.base_budget })
195 .collect();
196 }
197
198 algorithms
199 .iter()
200 .map(|algorithm| {
201 if algorithm.is_complete() {
202 0
203 } else {
204 let efficiency = self.get_average_efficiency(algorithm.name());
205 let proportion = efficiency / total_efficiency;
206 let weighted_budget = (self.base_budget as f32 * proportion * algorithms.len() as f32).round() as usize;
207 weighted_budget.max(1)
208 }
209 })
210 .collect()
211 }
212
213 fn name(&self) -> &str {
214 "Performance Weighted"
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::services::sorters::{bubble::BubbleSort, quick::QuickSort};
222
223 #[test]
224 fn test_weighted_fairness_different_algorithms() {
225 let fairness = WeightedFairness::new(1.0, 1.0); let mut bubble = BubbleSort::new();
228 let mut quick = QuickSort::new();
229 bubble.reset(vec![3, 1, 2]);
230 quick.reset(vec![3, 1, 2]);
231
232 let algorithms: Vec<Box<dyn Sorter>> = vec![
233 Box::new(bubble),
234 Box::new(quick),
235 ];
236
237 let budgets = fairness.allocate_budget(&algorithms);
238
239 assert!(budgets[0] > 0);
241 assert!(budgets[1] > 0);
242
243 let total: usize = budgets.iter().sum();
245 assert!(total > 0);
246 }
247
248 #[test]
249 fn test_performance_weighted_fairness() {
250 let mut fairness = PerformanceWeightedFairness::new(10, 5);
251
252 fairness.record_efficiency("Algorithm A", 2.0);
254 fairness.record_efficiency("Algorithm A", 1.5);
255 fairness.record_efficiency("Algorithm B", 1.0);
256
257 assert_eq!(fairness.get_average_efficiency("Algorithm A"), 1.75);
258 assert_eq!(fairness.get_average_efficiency("Algorithm B"), 1.0);
259 }
260
261 #[test]
262 fn test_weighted_fairness_alpha_beta_scoring() {
263 let fairness = WeightedFairness::new(2.0, 0.5); let mut high_comp = BubbleSort::new();
267 let mut high_move = BubbleSort::new();
268 high_comp.reset(vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); high_move.reset(vec![1, 3, 2, 4, 5, 7, 6, 8, 9, 10]); high_comp.step(20); high_move.step(5); let algorithms: Vec<Box<dyn Sorter>> = vec![
276 Box::new(high_comp),
277 Box::new(high_move),
278 ];
279
280 let budgets = fairness.allocate_budget(&algorithms);
281
282 assert_eq!(budgets.len(), 2);
283 assert!(budgets[0] > 0, "First algorithm should get budget > 0, got {}", budgets[0]);
284 assert!(budgets[1] > 0, "Second algorithm should get budget > 0, got {}", budgets[1]);
285
286 let total: usize = budgets.iter().sum();
288 assert!(total > 0);
289 }
290}