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
72/// A specialized `Result` type for DE operations.
73pub type Result<T> = std::result::Result<T, DEError>;
74
75impl DEError {
76    /// Returns `true` if this is a bounds-related error.
77    ///
78    /// This includes `BoundsMismatch` and `InvalidBounds` variants.
79    pub fn is_bounds_error(&self) -> bool {
80        matches!(
81            self,
82            DEError::BoundsMismatch { .. } | DEError::InvalidBounds { .. }
83        )
84    }
85
86    /// Returns `true` if this is a configuration-related error.
87    ///
88    /// This includes `PopulationTooSmall`, `InvalidMutationFactor`,
89    /// and `InvalidCrossoverRate` variants.
90    pub fn is_config_error(&self) -> bool {
91        matches!(
92            self,
93            DEError::PopulationTooSmall { .. }
94                | DEError::InvalidMutationFactor { .. }
95                | DEError::InvalidCrossoverRate { .. }
96        )
97    }
98
99    /// Returns `true` if this is a dimension mismatch error.
100    ///
101    /// This includes `X0DimensionMismatch` and `IntegralityDimensionMismatch`.
102    pub fn is_dimension_error(&self) -> bool {
103        matches!(
104            self,
105            DEError::X0DimensionMismatch { .. } | DEError::IntegralityDimensionMismatch { .. }
106        )
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_error_display() {
116        let err = DEError::BoundsMismatch {
117            lower_len: 3,
118            upper_len: 5,
119        };
120        assert_eq!(
121            err.to_string(),
122            "bounds mismatch: lower has 3 elements, upper has 5"
123        );
124    }
125
126    #[test]
127    fn test_is_bounds_error() {
128        let bounds_err = DEError::BoundsMismatch {
129            lower_len: 1,
130            upper_len: 2,
131        };
132        let config_err = DEError::PopulationTooSmall { pop_size: 2 };
133
134        assert!(bounds_err.is_bounds_error());
135        assert!(!config_err.is_bounds_error());
136    }
137
138    #[test]
139    fn test_is_config_error() {
140        let config_err = DEError::InvalidCrossoverRate { rate: 1.5 };
141        let bounds_err = DEError::InvalidBounds {
142            index: 0,
143            lower: 5.0,
144            upper: 3.0,
145        };
146
147        assert!(config_err.is_config_error());
148        assert!(!bounds_err.is_config_error());
149    }
150
151    #[test]
152    fn test_is_dimension_error() {
153        let dim_err = DEError::X0DimensionMismatch {
154            expected: 10,
155            got: 5,
156        };
157        let bounds_err = DEError::BoundsMismatch {
158            lower_len: 1,
159            upper_len: 2,
160        };
161
162        assert!(dim_err.is_dimension_error());
163        assert!(!bounds_err.is_dimension_error());
164    }
165}