sorting_race/services/fairness/
adaptive.rs1use crate::models::traits::{FairnessModel, Sorter};
4use std::collections::HashMap;
5use std::cell::RefCell;
6
7#[derive(Debug)]
10pub struct AdaptiveFairness {
11 learning_rate: f32,
12 base_budget: usize,
13 progress_rates: RefCell<HashMap<String, f32>>, previous_progress: RefCell<HashMap<String, f32>>, step_count: RefCell<HashMap<String, u64>>, }
17
18impl AdaptiveFairness {
19 pub fn new(learning_rate: f32) -> Self {
24 Self {
25 learning_rate: learning_rate.clamp(0.0, 1.0),
26 base_budget: 100,
27 progress_rates: RefCell::new(HashMap::new()),
28 previous_progress: RefCell::new(HashMap::new()),
29 step_count: RefCell::new(HashMap::new()),
30 }
31 }
32
33 fn update_progress_rate(&self, algorithm: &dyn Sorter) {
35 let name = algorithm.name();
36 let current_progress = algorithm.get_telemetry().progress_hint;
37
38 let mut progress_rates = self.progress_rates.borrow_mut();
39 let mut previous_progress = self.previous_progress.borrow_mut();
40 let mut step_count = self.step_count.borrow_mut();
41
42 let prev_progress = previous_progress.get(name).copied().unwrap_or(0.0);
44
45 let progress_delta = current_progress - prev_progress;
47 let current_rate = progress_delta.max(0.0); let old_rate = progress_rates.get(name).copied().unwrap_or(current_rate);
52 let new_rate = (1.0 - self.learning_rate) * old_rate + self.learning_rate * current_rate;
53
54 progress_rates.insert(name.to_string(), new_rate);
55 previous_progress.insert(name.to_string(), current_progress);
56
57 let steps = step_count.get(name).copied().unwrap_or(0) + 1;
59 step_count.insert(name.to_string(), steps);
60 }
61
62 fn get_progress_rate(&self, algorithm_name: &str) -> f32 {
64 self.progress_rates.borrow().get(algorithm_name).copied().unwrap_or(0.01) }
66
67 pub fn set_base_budget(&mut self, budget: usize) {
69 self.base_budget = budget.max(1);
70 }
71
72 pub fn clear_history(&self) {
74 self.progress_rates.borrow_mut().clear();
75 self.previous_progress.borrow_mut().clear();
76 self.step_count.borrow_mut().clear();
77 }
78
79 pub fn get_learning_rate(&self) -> f32 {
81 self.learning_rate
82 }
83}
84
85impl Default for AdaptiveFairness {
86 fn default() -> Self {
87 Self::new(0.2) }
89}
90
91impl FairnessModel for AdaptiveFairness {
92 fn allocate_budget(&self, algorithms: &[Box<dyn Sorter>]) -> Vec<usize> {
93 for algorithm in algorithms.iter() {
95 if !algorithm.is_complete() {
96 self.update_progress_rate(algorithm.as_ref());
97 }
98 }
99
100 let active_algos: Vec<(usize, f32)> = algorithms
102 .iter()
103 .enumerate()
104 .filter_map(|(i, alg)| {
105 if alg.is_complete() {
106 None
107 } else {
108 let rate = self.get_progress_rate(alg.name());
109 Some((i, rate))
110 }
111 })
112 .collect();
113
114 if active_algos.is_empty() {
116 return vec![0; algorithms.len()];
117 }
118
119 if active_algos.len() == 1 {
120 let mut budgets = vec![0; algorithms.len()];
121 budgets[active_algos[0].0] = self.base_budget;
122 return budgets;
123 }
124
125 let total_budget = self.base_budget * active_algos.len();
127
128 let max_rate = active_algos.iter().map(|(_, rate)| *rate).fold(0.0f32, f32::max);
130 let min_rate = active_algos.iter().map(|(_, rate)| *rate).fold(f32::INFINITY, f32::min);
131
132 let mut budgets = vec![0; algorithms.len()];
133
134 if max_rate == min_rate || max_rate == 0.0 {
135 let equal_budget = total_budget / active_algos.len();
137 for (idx, _) in active_algos {
138 budgets[idx] = equal_budget;
139 }
140 } else {
141 let total_inverse: f32 = active_algos.iter()
143 .map(|(_, rate)| max_rate - rate + 0.01) .sum();
145
146 for (idx, rate) in active_algos {
147 let inverse_weight = max_rate - rate + 0.01;
148 let proportion = inverse_weight / total_inverse;
149 let budget = ((total_budget as f32 * proportion).round() as usize).max(1);
150 budgets[idx] = budget;
151 }
152 }
153
154 budgets
155 }
156
157 fn name(&self) -> &str {
158 "Adaptive Fairness"
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::models::traits::Telemetry;
166
167 #[derive(Debug)]
168 struct MockSorter {
169 name: String,
170 complete: bool,
171 progress: f32,
172 }
173
174 impl MockSorter {
175 fn new(name: &str, progress: f32) -> Self {
176 Self {
177 name: name.to_string(),
178 complete: false,
179 progress,
180 }
181 }
182 }
183
184 impl Sorter for MockSorter {
185 fn step(&mut self, _budget: usize) -> crate::models::traits::StepResult {
186 unimplemented!()
187 }
188
189 fn is_complete(&self) -> bool {
190 self.complete
191 }
192
193 fn get_telemetry(&self) -> Telemetry {
194 Telemetry {
195 total_comparisons: 0,
196 total_moves: 0,
197 memory_current: 0,
198 memory_peak: 0,
199 highlights: vec![],
200 markers: crate::models::traits::Markers::default(),
201 status_text: String::new(),
202 progress_hint: self.progress,
203 }
204 }
205
206 fn reset(&mut self, _data: Vec<i32>) {}
207
208 fn name(&self) -> &str {
209 &self.name
210 }
211
212 fn get_array(&self) -> &[i32] {
213 &[]
214 }
215
216 fn get_memory_usage(&self) -> usize {
217 0
218 }
219
220 fn as_any(&self) -> &dyn std::any::Any {
221 self
222 }
223
224 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
225 self
226 }
227 }
228
229 #[test]
230 fn test_adaptive_fairness_creation() {
231 let model = AdaptiveFairness::new(0.2);
232 assert_eq!(model.learning_rate, 0.2);
233 assert_eq!(model.name(), "Adaptive Fairness");
234 }
235
236 #[test]
237 fn test_adaptive_fairness_budget_allocation() {
238 let model = AdaptiveFairness::new(0.1);
239 let algorithms: Vec<Box<dyn Sorter>> = vec![
240 Box::new(MockSorter::new("Test1", 0.5)),
241 Box::new(MockSorter::new("Test2", 0.3)),
242 ];
243
244 let budgets = model.allocate_budget(&algorithms);
245
246 assert_eq!(budgets.len(), 2);
247 assert!(budgets[0] > 0);
248 assert!(budgets[1] > 0);
249 }
250}