scirs2_stats/
error.rs

1//! Error types for the SciRS2 statistics module
2
3use scirs2_core::error::{CoreError, ErrorContext, ErrorLocation};
4use thiserror::Error;
5
6/// Statistics error type
7#[derive(Error, Debug, Clone)]
8pub enum StatsError {
9    /// Computation error (generic error)
10    #[error("Computation error: {0}")]
11    ComputationError(String),
12
13    /// Domain error (input outside valid domain)
14    #[error("Domain error: {0}")]
15    DomainError(String),
16
17    /// Dimension mismatch error
18    #[error("Dimension mismatch error: {0}")]
19    DimensionMismatch(String),
20
21    /// Value error (invalid value)
22    #[error("Invalid argument: {0}")]
23    InvalidArgument(String),
24
25    /// Not implemented error
26    #[error("Not implemented: {0}")]
27    NotImplementedError(String),
28
29    /// Convergence error (algorithm failed to converge)
30    #[error("Convergence error: {0}")]
31    ConvergenceError(String),
32
33    /// Insufficient data error
34    #[error("Insufficient data: {0}")]
35    InsufficientData(String),
36
37    /// Invalid input error
38    #[error("Invalid input: {0}")]
39    InvalidInput(String),
40
41    /// Not implemented (alias for backwards compatibility)
42    #[error("Not implemented: {0}")]
43    NotImplemented(String),
44
45    /// Core error (propagated from scirs2-core)
46    #[error("{0}")]
47    CoreError(#[from] CoreError),
48
49    /// Random distribution error
50    #[error("Random distribution error: {0}")]
51    DistributionError(String),
52}
53
54// The #[from] attribute in the CoreError variant handles the conversion automatically
55
56// NOTE: rand, distr: uniform::Error API has changed, commenting out for now
57// impl From<scirs2_core::random::uniform::Error> for StatsError {
58//     fn from(err: rand, distr: uniform::Error) -> Self {
59//         StatsError::DistributionError(format!("Uniform distribution error: {}", err))
60//     }
61// }
62
63/// Helper trait for adding context and recovery suggestions to errors
64pub trait StatsErrorExt {
65    /// Add context information to the error
66    fn context<S: Into<String>>(self, context: S) -> Self;
67
68    /// Add a recovery suggestion to the error
69    fn suggestion<S: Into<String>>(self, suggestion: S) -> Self;
70}
71
72impl StatsError {
73    /// Create a computation error with context
74    pub fn computation<S: Into<String>>(message: S) -> Self {
75        StatsError::ComputationError(message.into())
76    }
77
78    /// Create a domain error with context
79    pub fn domain<S: Into<String>>(message: S) -> Self {
80        StatsError::DomainError(message.into())
81    }
82
83    /// Create a dimension mismatch error with context
84    pub fn dimension_mismatch<S: Into<String>>(message: S) -> Self {
85        StatsError::DimensionMismatch(message.into())
86    }
87
88    /// Create an invalid argument error with context
89    pub fn invalid_argument<S: Into<String>>(message: S) -> Self {
90        StatsError::InvalidArgument(message.into())
91    }
92
93    /// Create a not implemented error with context
94    pub fn not_implemented<S: Into<String>>(message: S) -> Self {
95        StatsError::NotImplementedError(message.into())
96    }
97
98    /// Create an insufficient data error with context
99    pub fn insufficientdata<S: Into<String>>(message: S) -> Self {
100        StatsError::InsufficientData(message.into())
101    }
102
103    /// Create an invalid input error with context
104    pub fn invalid_input<S: Into<String>>(message: S) -> Self {
105        StatsError::InvalidInput(message.into())
106    }
107
108    /// Add recovery suggestions based on error type
109    pub fn with_suggestion(&self) -> String {
110        match self {
111            StatsError::DomainError(msg) => {
112                if msg.contains("must be positive") {
113                    format!(
114                        "{msg}
115Suggestion: Ensure the value is greater than 0"
116                    )
117                } else if msg.contains("probability") {
118                    format!(
119                        "{msg}
120Suggestion: Probability values must be between 0 and 1 (inclusive)"
121                    )
122                } else if msg.contains("degrees of freedom") {
123                    format!(
124                        "{msg}
125Suggestion: Degrees of freedom must be a positive value"
126                    )
127                } else {
128                    msg.clone()
129                }
130            }
131            StatsError::DimensionMismatch(msg) => {
132                if msg.contains("same length") {
133                    format!(
134                        "{msg}
135Suggestion: Ensure both arrays have the same number of elements"
136                    )
137                } else if msg.contains("square matrix") {
138                    format!(
139                        "{msg}
140Suggestion: The input matrix must have equal number of rows and columns"
141                    )
142                } else {
143                    format!(
144                        "{msg}
145Suggestion: Check that input dimensions match the function requirements"
146                    )
147                }
148            }
149            StatsError::InvalidArgument(msg) => {
150                if msg.contains("empty") {
151                    format!(
152                        "{msg}
153Suggestion: Provide a non-empty array or collection"
154                    )
155                } else if msg.contains("NaN") || msg.contains("nan") {
156                    format!(
157                        "{msg}
158Suggestion: Remove or handle NaN values before computation"
159                    )
160                } else if msg.contains("infinite") || msg.contains("inf") {
161                    format!(
162                        "{msg}
163Suggestion: Check for and handle infinite values in your data"
164                    )
165                } else {
166                    format!(
167                        "{msg}
168Suggestion: Verify that all input arguments meet the function requirements"
169                    )
170                }
171            }
172            StatsError::NotImplementedError(msg) => {
173                format!("{msg}
174Suggestion: This feature is not yet available. Consider using an alternative method or check for updates")
175            }
176            StatsError::ComputationError(msg) => {
177                if msg.contains("overflow") {
178                    format!(
179                        "{msg}
180Suggestion: Try scaling your input data or using a more numerically stable algorithm"
181                    )
182                } else if msg.contains("convergence") {
183                    format!(
184                        "{msg}
185Suggestion: Try adjusting convergence parameters or using different initial values"
186                    )
187                } else {
188                    format!(
189                        "{msg}
190Suggestion: Check input data for numerical issues or extreme values"
191                    )
192                }
193            }
194            StatsError::ConvergenceError(msg) => {
195                format!("{msg}
196Suggestion: Try adjusting convergence parameters, using different initial values, or increasing the maximum number of iterations")
197            }
198            StatsError::InsufficientData(msg) => {
199                format!(
200                    "{msg}
201Suggestion: Increase sample size or use methods designed for small datasets"
202                )
203            }
204            StatsError::InvalidInput(msg) => {
205                format!(
206                    "{msg}
207Suggestion: Check input format and ensure data meets function requirements"
208                )
209            }
210            StatsError::NotImplemented(msg) => {
211                format!("{msg}
212Suggestion: This feature is not yet available. Consider using an alternative method or check for updates")
213            }
214            StatsError::CoreError(err) => {
215                format!(
216                    "{err}
217Suggestion: {}",
218                    "Refer to the core error for more details"
219                )
220            }
221            StatsError::DistributionError(msg) => {
222                format!(
223                    "{msg}
224Suggestion: Check distribution parameters and ensure they are within valid ranges"
225                )
226            }
227        }
228    }
229}
230
231/// Result type for statistics operations
232pub type StatsResult<T> = Result<T, StatsError>;
233
234/// Create a function to convert from StatsResult to CoreError::ValidationError
235#[allow(dead_code)]
236pub fn convert_to_validation_error<T, S: Into<String>>(
237    result: StatsResult<T>,
238    message: S,
239) -> Result<T, CoreError> {
240    match result {
241        Ok(val) => Ok(val),
242        Err(err) => Err(CoreError::ValidationError(
243            ErrorContext::new(format!("{}: {err}", message.into()))
244                .with_location(ErrorLocation::new(file!(), line!())),
245        )),
246    }
247}