sorting_race/models/
configuration.rs

1//! Configuration state for interactive terminal interface
2
3use crate::models::config::{Distribution, FairnessMode};
4use anyhow::{Result, anyhow};
5
6/// Represents current user selections for interactive configuration
7#[derive(Debug, Clone, PartialEq)]
8pub struct ConfigurationState {
9    /// Number of elements to sort (10-1000)
10    pub array_size: u32,
11    /// Data pattern selection
12    pub distribution: DistributionType,
13    /// Algorithm fairness strategy
14    pub fairness_mode: FairnessMode,
15    /// Budget parameter for comparison fairness
16    pub budget: Option<u32>,
17    /// Alpha parameter for weighted fairness
18    pub alpha: Option<f32>,
19    /// Beta parameter for weighted fairness
20    pub beta: Option<f32>,
21    /// Learning rate for adaptive fairness
22    pub learning_rate: Option<f32>,
23}
24
25/// Distribution types for interactive configuration
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum DistributionType {
28    Shuffled,
29    Reversed,
30    NearlySorted,
31    FewUnique,
32}
33
34impl ConfigurationState {
35    /// Create a new configuration state with default values
36    pub fn new() -> Self {
37        Self {
38            array_size: 100,
39            distribution: DistributionType::Shuffled,
40            fairness_mode: FairnessMode::WallTime { slice_ms: 50 },
41            budget: None,
42            alpha: None,
43            beta: None,
44            learning_rate: None,
45        }
46    }
47
48    /// Create a default valid configuration for testing
49    pub fn default_valid() -> Self {
50        Self::new()
51    }
52
53    /// Create ConfigurationState from RunConfiguration for backwards compatibility
54    pub fn from_run_config(config: &crate::models::config::RunConfiguration) -> Self {
55        Self {
56            array_size: config.array_size as u32,
57            distribution: config.distribution.clone().into(),
58            fairness_mode: config.fairness_mode.clone(),
59            budget: None,
60            alpha: None,
61            beta: None,
62            learning_rate: None,
63        }
64    }
65
66    /// Get available array sizes for interactive selection
67    pub fn get_available_array_sizes() -> Vec<u32> {
68        vec![10, 25, 50, 100, 200, 500, 1000]
69    }
70
71    /// Get available distribution types
72    pub fn get_available_distributions() -> Vec<DistributionType> {
73        vec![
74            DistributionType::Shuffled,
75            DistributionType::Reversed,
76            DistributionType::NearlySorted,
77            DistributionType::FewUnique,
78        ]
79    }
80
81    /// Get available fairness modes
82    pub fn get_available_fairness_modes() -> Vec<FairnessMode> {
83        vec![
84            FairnessMode::ComparisonBudget { k: 16 },
85            FairnessMode::Weighted { alpha: 2.0, beta: 0.5 },
86            FairnessMode::WallTime { slice_ms: 50 },
87            FairnessMode::Adaptive { learning_rate: 0.3 },
88        ]
89    }
90
91    /// Check if current fairness mode requires budget parameter
92    pub fn requires_budget_parameter(&self) -> bool {
93        matches!(self.fairness_mode, FairnessMode::ComparisonBudget { .. })
94    }
95
96    /// Check if current fairness mode requires weighted parameters (alpha/beta)
97    pub fn requires_weighted_parameters(&self) -> bool {
98        matches!(self.fairness_mode, FairnessMode::Weighted { .. })
99    }
100
101    /// Check if current fairness mode requires learning rate parameter
102    pub fn requires_learning_rate_parameter(&self) -> bool {
103        matches!(self.fairness_mode, FairnessMode::Adaptive { .. })
104    }
105
106    /// Validate the current configuration
107    pub fn validate(&self) -> Result<()> {
108        // Validate array size
109        if self.array_size < 10 || self.array_size > 1000 {
110            return Err(anyhow!("Array size must be between 10 and 1000, got {}", self.array_size));
111        }
112
113        // Validate fairness mode parameters
114        match &self.fairness_mode {
115            FairnessMode::ComparisonBudget { k } => {
116                if *k == 0 {
117                    return Err(anyhow!("Budget parameter must be greater than 0, got {}", k));
118                }
119            },
120            FairnessMode::Weighted { alpha, beta } => {
121                if *alpha <= 0.0 {
122                    return Err(anyhow!("Alpha parameter must be greater than 0.0, got {}", alpha));
123                }
124                if *beta <= 0.0 {
125                    return Err(anyhow!("Beta parameter must be greater than 0.0, got {}", beta));
126                }
127            },
128            FairnessMode::Adaptive { learning_rate } => {
129                if *learning_rate < 0.1 || *learning_rate > 1.0 {
130                    return Err(anyhow!("Learning rate must be between 0.1 and 1.0, got {}", learning_rate));
131                }
132            },
133            FairnessMode::WallTime { slice_ms } => {
134                if *slice_ms == 0 {
135                    return Err(anyhow!("Wall time slice must be greater than 0, got {}", slice_ms));
136                }
137            },
138            _ => {}, // Other fairness modes don't require validation
139        }
140
141        Ok(())
142    }
143
144    /// Check if the configuration is valid
145    pub fn is_valid(&self) -> bool {
146        self.validate().is_ok()
147    }
148
149    /// Check if the configuration is invalid
150    pub fn is_invalid(&self) -> bool {
151        !self.is_valid()
152    }
153
154    /// Set array size with validation
155    pub fn set_array_size(&mut self, size: u32) -> Result<()> {
156        if !(10..=1000).contains(&size) {
157            return Err(anyhow!("Array size must be between 10 and 1000, got {}", size));
158        }
159        self.array_size = size;
160        Ok(())
161    }
162
163    /// Set fairness mode and clear incompatible parameters
164    pub fn set_fairness_mode(&mut self, mode: FairnessMode) {
165        self.fairness_mode = mode.clone();
166
167        // Clear parameters that don't apply to the new mode
168        match &mode {
169            FairnessMode::ComparisonBudget { .. } => {
170                self.alpha = None;
171                self.beta = None;
172                self.learning_rate = None;
173            },
174            FairnessMode::Weighted { .. } => {
175                self.budget = None;
176                self.learning_rate = None;
177            },
178            FairnessMode::Adaptive { .. } => {
179                self.budget = None;
180                self.alpha = None;
181                self.beta = None;
182            },
183            FairnessMode::WallTime { .. } => {
184                self.budget = None;
185                self.alpha = None;
186                self.beta = None;
187                self.learning_rate = None;
188            },
189            _ => {
190                // Clear all optional parameters
191                self.budget = None;
192                self.alpha = None;
193                self.beta = None;
194                self.learning_rate = None;
195            },
196        }
197    }
198
199    /// Convert to the legacy Distribution enum for compatibility
200    pub fn to_legacy_distribution(&self) -> Distribution {
201        match self.distribution {
202            DistributionType::Shuffled => Distribution::Shuffled,
203            DistributionType::Reversed => Distribution::Reversed,
204            DistributionType::NearlySorted => Distribution::NearlySorted,
205            DistributionType::FewUnique => Distribution::FewUnique,
206        }
207    }
208}
209
210impl Default for ConfigurationState {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216impl From<DistributionType> for Distribution {
217    fn from(dist_type: DistributionType) -> Self {
218        match dist_type {
219            DistributionType::Shuffled => Distribution::Shuffled,
220            DistributionType::Reversed => Distribution::Reversed,
221            DistributionType::NearlySorted => Distribution::NearlySorted,
222            DistributionType::FewUnique => Distribution::FewUnique,
223        }
224    }
225}
226
227impl From<Distribution> for DistributionType {
228    fn from(distribution: Distribution) -> Self {
229        match distribution {
230            Distribution::Shuffled => DistributionType::Shuffled,
231            Distribution::Reversed => DistributionType::Reversed,
232            Distribution::NearlySorted => DistributionType::NearlySorted,
233            Distribution::FewUnique => DistributionType::FewUnique,
234            Distribution::Sorted => DistributionType::Shuffled,     // Map to closest equivalent
235            Distribution::WithDuplicates => DistributionType::FewUnique,  // Map to closest equivalent
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_configuration_state_creation() {
246        let config = ConfigurationState::new();
247        assert_eq!(config.array_size, 100);
248        assert_eq!(config.distribution, DistributionType::Shuffled);
249        assert!(matches!(config.fairness_mode, FairnessMode::WallTime { .. }));
250        assert_eq!(config.budget, None);
251        assert_eq!(config.alpha, None);
252        assert_eq!(config.beta, None);
253        assert_eq!(config.learning_rate, None);
254    }
255
256    #[test]
257    fn test_array_size_validation() {
258        let mut config = ConfigurationState::new();
259        
260        // Valid sizes
261        assert!(config.set_array_size(50).is_ok());
262        assert_eq!(config.array_size, 50);
263        
264        assert!(config.set_array_size(10).is_ok());
265        assert!(config.set_array_size(1000).is_ok());
266        
267        // Invalid sizes
268        assert!(config.set_array_size(5).is_err());
269        assert!(config.set_array_size(1001).is_err());
270    }
271
272    #[test]
273    fn test_fairness_mode_parameter_requirements() {
274        let mut config = ConfigurationState::new();
275        
276        config.set_fairness_mode(FairnessMode::ComparisonBudget { k: 16 });
277        assert!(config.requires_budget_parameter());
278        assert!(!config.requires_weighted_parameters());
279        assert!(!config.requires_learning_rate_parameter());
280        
281        config.set_fairness_mode(FairnessMode::Weighted { alpha: 2.0, beta: 0.5 });
282        assert!(!config.requires_budget_parameter());
283        assert!(config.requires_weighted_parameters());
284        assert!(!config.requires_learning_rate_parameter());
285        
286        config.set_fairness_mode(FairnessMode::Adaptive { learning_rate: 0.3 });
287        assert!(!config.requires_budget_parameter());
288        assert!(!config.requires_weighted_parameters());
289        assert!(config.requires_learning_rate_parameter());
290    }
291
292    #[test]
293    fn test_configuration_validation() {
294        let mut config = ConfigurationState::new();
295        assert!(config.is_valid());
296        
297        // Invalid array size
298        config.array_size = 5;
299        assert!(config.is_invalid());
300        
301        config.array_size = 100; // Reset to valid
302        assert!(config.is_valid());
303        
304        // Invalid fairness parameters
305        config.fairness_mode = FairnessMode::ComparisonBudget { k: 0 };
306        assert!(config.is_invalid());
307    }
308
309    #[test]
310    fn test_available_options() {
311        let sizes = ConfigurationState::get_available_array_sizes();
312        assert_eq!(sizes, vec![10, 25, 50, 100, 200, 500, 1000]);
313        
314        let distributions = ConfigurationState::get_available_distributions();
315        assert_eq!(distributions.len(), 4);
316        assert!(distributions.contains(&DistributionType::Shuffled));
317        
318        let fairness_modes = ConfigurationState::get_available_fairness_modes();
319        assert_eq!(fairness_modes.len(), 4);
320    }
321
322    #[test]
323    fn test_distribution_conversion() {
324        let dist_type = DistributionType::Reversed;
325        let legacy_dist: Distribution = dist_type.into();
326        assert_eq!(legacy_dist, Distribution::Reversed);
327        
328        let config = ConfigurationState::new();
329        let legacy = config.to_legacy_distribution();
330        assert_eq!(legacy, Distribution::Shuffled);
331    }
332}