Skip to main content

math_audio_optimisation/
error.rs

1//! Error types for the Differential Evolution optimizer.
2//!
3//! This module provides structured error handling for DE optimization,
4//! following the Microsoft Rust Guidelines pattern of using `thiserror`
5//! for library error types with helper methods for error categorization.
6
7use thiserror::Error;
8
9/// Errors that can occur during Differential Evolution optimization.
10#[derive(Debug, Error)]
11pub enum DEError {
12    /// Lower and upper bounds have different lengths.
13    #[error("bounds mismatch: lower has {lower_len} elements, upper has {upper_len}")]
14    BoundsMismatch {
15        /// Length of the lower bounds array
16        lower_len: usize,
17        /// Length of the upper bounds array
18        upper_len: usize,
19    },
20
21    /// A lower bound exceeds its corresponding upper bound.
22    #[error("invalid bounds at index {index}: lower ({lower}) > upper ({upper})")]
23    InvalidBounds {
24        /// Index of the invalid bound pair
25        index: usize,
26        /// The lower bound value
27        lower: f64,
28        /// The upper bound value
29        upper: f64,
30    },
31
32    /// Population size is too small (must be >= 4).
33    #[error("population size ({pop_size}) must be >= 4 for DE algorithm")]
34    PopulationTooSmall {
35        /// The invalid population size
36        pop_size: usize,
37    },
38
39    /// Mutation factor is out of valid range [0, 2].
40    #[error("invalid mutation factor: {factor} (must be in [0, 2])")]
41    InvalidMutationFactor {
42        /// The invalid mutation factor
43        factor: f64,
44    },
45
46    /// Crossover rate is out of valid range [0, 1].
47    #[error("invalid crossover rate: {rate} (must be in [0, 1])")]
48    InvalidCrossoverRate {
49        /// The invalid crossover rate
50        rate: f64,
51    },
52
53    /// Initial guess (x0) has wrong dimension.
54    #[error("x0 dimension mismatch: expected {expected}, got {got}")]
55    X0DimensionMismatch {
56        /// Expected dimension
57        expected: usize,
58        /// Actual dimension provided
59        got: usize,
60    },
61
62    /// Integrality mask has wrong dimension.
63    #[error("integrality mask dimension mismatch: expected {expected}, got {got}")]
64    IntegralityDimensionMismatch {
65        /// Expected dimension
66        expected: usize,
67        /// Actual dimension provided
68        got: usize,
69    },
70
71    /// Generic invalid optimizer configuration.
72    #[error("invalid optimizer configuration: {message}")]
73    InvalidConfig {
74        /// Human-readable description of the invalid setting.
75        message: String,
76    },
77}
78
79/// A specialized `Result` type for DE operations.
80pub type Result<T> = std::result::Result<T, DEError>;
81
82impl DEError {
83    /// Returns `true` if this is a bounds-related error.
84    ///
85    /// This includes `BoundsMismatch` and `InvalidBounds` variants.
86    pub fn is_bounds_error(&self) -> bool {
87        matches!(
88            self,
89            DEError::BoundsMismatch { .. } | DEError::InvalidBounds { .. }
90        )
91    }
92
93    /// Returns `true` if this is a configuration-related error.
94    ///
95    /// This includes `PopulationTooSmall`, `InvalidMutationFactor`,
96    /// and `InvalidCrossoverRate` variants.
97    pub fn is_config_error(&self) -> bool {
98        matches!(
99            self,
100            DEError::PopulationTooSmall { .. }
101                | DEError::InvalidMutationFactor { .. }
102                | DEError::InvalidCrossoverRate { .. }
103                | DEError::InvalidConfig { .. }
104        )
105    }
106
107    /// Returns `true` if this is a dimension mismatch error.
108    ///
109    /// This includes `X0DimensionMismatch` and `IntegralityDimensionMismatch`.
110    pub fn is_dimension_error(&self) -> bool {
111        matches!(
112            self,
113            DEError::X0DimensionMismatch { .. } | DEError::IntegralityDimensionMismatch { .. }
114        )
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_error_display() {
124        let err = DEError::BoundsMismatch {
125            lower_len: 3,
126            upper_len: 5,
127        };
128        assert_eq!(
129            err.to_string(),
130            "bounds mismatch: lower has 3 elements, upper has 5"
131        );
132    }
133
134    #[test]
135    fn test_is_bounds_error() {
136        let bounds_err = DEError::BoundsMismatch {
137            lower_len: 1,
138            upper_len: 2,
139        };
140        let config_err = DEError::PopulationTooSmall { pop_size: 2 };
141
142        assert!(bounds_err.is_bounds_error());
143        assert!(!config_err.is_bounds_error());
144    }
145
146    #[test]
147    fn test_is_config_error() {
148        let config_err = DEError::InvalidCrossoverRate { rate: 1.5 };
149        let bounds_err = DEError::InvalidBounds {
150            index: 0,
151            lower: 5.0,
152            upper: 3.0,
153        };
154
155        assert!(config_err.is_config_error());
156        assert!(!bounds_err.is_config_error());
157    }
158
159    #[test]
160    fn test_is_dimension_error() {
161        let dim_err = DEError::X0DimensionMismatch {
162            expected: 10,
163            got: 5,
164        };
165        let bounds_err = DEError::BoundsMismatch {
166            lower_len: 1,
167            upper_len: 2,
168        };
169
170        assert!(dim_err.is_dimension_error());
171        assert!(!bounds_err.is_dimension_error());
172    }
173}